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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (605) hide show
  1. package/README.md +33 -16
  2. package/dist/browser-runtime.js +14814 -14255
  3. package/dist/browser-runtime.js.map +4 -4
  4. package/dist/carp-catalog.d.ts +24 -19
  5. package/dist/carp-catalog.d.ts.map +1 -1
  6. package/dist/carp-catalog.js +29 -14
  7. package/dist/carp-catalog.js.map +4 -4
  8. package/dist/catalog-fec-params.d.ts +88 -28
  9. package/dist/catalog-fec-params.d.ts.map +1 -1
  10. package/dist/catalog-fec-params.js +60 -23
  11. package/dist/catalog-fec-params.js.map +4 -4
  12. package/dist/catalog-network-source.js +1 -7
  13. package/dist/catalog-network-source.js.map +4 -4
  14. package/dist/cmaf-block-assembler.d.ts +0 -2
  15. package/dist/cmaf-block-assembler.d.ts.map +1 -1
  16. package/dist/cmaf-block-assembler.js +0 -1
  17. package/dist/cmaf-block-assembler.js.map +2 -2
  18. package/dist/cmcd-producer.d.ts.map +1 -1
  19. package/dist/cmcd-producer.js +0 -4
  20. package/dist/cmcd-producer.js.map +2 -2
  21. package/dist/delivery-catalog.d.ts +124 -0
  22. package/dist/delivery-catalog.d.ts.map +1 -0
  23. package/dist/delivery-catalog.js +1857 -0
  24. package/dist/delivery-catalog.js.map +7 -0
  25. package/dist/delta-fallback-config.d.ts +42 -0
  26. package/dist/delta-fallback-config.d.ts.map +1 -0
  27. package/dist/delta-fallback-config.js +59 -0
  28. package/dist/delta-fallback-config.js.map +7 -0
  29. package/dist/fec-manager.d.ts +52 -30
  30. package/dist/fec-manager.d.ts.map +1 -1
  31. package/dist/fec-manager.js +338 -293
  32. package/dist/fec-manager.js.map +4 -4
  33. package/dist/fec-state.d.ts +19 -31
  34. package/dist/fec-state.d.ts.map +1 -1
  35. package/dist/fec-state.js +4 -10
  36. package/dist/fec-state.js.map +4 -4
  37. package/dist/fec-worker-setup.d.ts +18 -14
  38. package/dist/fec-worker-setup.d.ts.map +1 -1
  39. package/dist/fec-worker-setup.js +81 -96
  40. package/dist/fec-worker-setup.js.map +4 -4
  41. package/dist/index.d.ts +5 -3
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +16 -0
  44. package/dist/index.js.map +2 -2
  45. package/dist/iso-block-locator.js +1 -7
  46. package/dist/iso-block-locator.js.map +4 -4
  47. package/dist/media-router.d.ts +20 -8
  48. package/dist/media-router.d.ts.map +1 -1
  49. package/dist/media-router.js +46 -21
  50. package/dist/media-router.js.map +2 -2
  51. package/dist/mfu-reassembler.js +24 -30
  52. package/dist/mfu-reassembler.js.map +4 -4
  53. package/dist/mmtp-container-parser.d.ts +14 -11
  54. package/dist/mmtp-container-parser.d.ts.map +1 -1
  55. package/dist/mmtp-container-parser.js +126 -99
  56. package/dist/mmtp-container-parser.js.map +4 -4
  57. package/dist/mmtp-container-parser.no-modules.js +151 -108
  58. package/dist/mmtp-container-parser.no-modules.js.map +4 -4
  59. package/dist/mmtp-decode-pipeline.js +126 -99
  60. package/dist/mmtp-decode-pipeline.js.map +4 -4
  61. package/dist/mmtp-router.d.ts +51 -29
  62. package/dist/mmtp-router.d.ts.map +1 -1
  63. package/dist/mmtp-router.js +260 -326
  64. package/dist/mmtp-router.js.map +4 -4
  65. package/dist/multicast-input.js +1 -7
  66. package/dist/multicast-input.js.map +4 -4
  67. package/dist/multicast-manager.d.ts +72 -6
  68. package/dist/multicast-manager.d.ts.map +1 -1
  69. package/dist/multicast-manager.js +882 -673
  70. package/dist/multicast-manager.js.map +4 -4
  71. package/dist/multicast-transport.d.ts +1 -1
  72. package/dist/multicast-transport.d.ts.map +1 -1
  73. package/dist/multicast-transport.js +44 -26
  74. package/dist/multicast-transport.js.map +4 -4
  75. package/dist/multicast-unwrap.js +29 -35
  76. package/dist/multicast-unwrap.js.map +4 -4
  77. package/dist/no-modules.js +147 -106
  78. package/dist/no-modules.js.map +4 -4
  79. package/dist/repair-fetch.d.ts +116 -21
  80. package/dist/repair-fetch.d.ts.map +1 -1
  81. package/dist/repair-fetch.js +114 -34
  82. package/dist/repair-fetch.js.map +4 -4
  83. package/dist/repair-layers.d.ts +18 -7
  84. package/dist/repair-layers.d.ts.map +1 -1
  85. package/dist/repair-layers.js +12 -10
  86. package/dist/repair-layers.js.map +4 -4
  87. package/dist/signaling.js +2 -11
  88. package/dist/signaling.js.map +4 -4
  89. package/dist/timestamp-codec.js +24 -30
  90. package/dist/timestamp-codec.js.map +4 -4
  91. package/dist/track-session.d.ts +7 -0
  92. package/dist/track-session.d.ts.map +1 -1
  93. package/dist/track-session.js +15 -8
  94. package/dist/track-session.js.map +2 -2
  95. package/dist/wasm-mfu-reassembler.js +1 -7
  96. package/dist/wasm-mfu-reassembler.js.map +4 -4
  97. package/dist/wasm-mmtp-container-parser.js +126 -99
  98. package/dist/wasm-mmtp-container-parser.js.map +4 -4
  99. package/package.json +7 -14
  100. package/schemas/catalog.schema.json +3 -3
  101. package/src/carp-catalog.ts +46 -24
  102. package/src/catalog-fec-params.ts +173 -64
  103. package/src/cmaf-block-assembler.ts +1 -4
  104. package/src/cmcd-producer.ts +0 -6
  105. package/src/delivery-catalog.ts +498 -0
  106. package/src/delta-fallback-config.ts +72 -0
  107. package/src/fec-manager.ts +457 -334
  108. package/src/fec-state.ts +20 -32
  109. package/src/fec-worker-setup.ts +109 -90
  110. package/src/index.ts +32 -0
  111. package/src/media-router.ts +59 -21
  112. package/src/mmtp-container-parser.ts +106 -20
  113. package/src/mmtp-router.ts +189 -330
  114. package/src/multicast-manager.ts +265 -86
  115. package/src/multicast-transport.ts +16 -9
  116. package/src/repair-fetch.ts +273 -43
  117. package/src/repair-layers.ts +28 -8
  118. package/src/track-session.ts +28 -9
  119. package/test-vectors/catalog/README.md +37 -17
  120. package/test-vectors/catalog/negative/fec-block-profile-retired.json +52 -0
  121. package/test-vectors/catalog/negative/fec-enhancement-repair-removed.json +1 -1
  122. package/test-vectors/catalog/negative/fec-repair-layer-geometry.json +1 -1
  123. package/test-vectors/catalog/negative/fec-repair-layer-missing-symbols.json +1 -1
  124. package/test-vectors/catalog/negative/fec-repair-multiple-depends.json +1 -1
  125. package/test-vectors/catalog/negative/fec-repair-overlay-with-layer.json +1 -1
  126. package/test-vectors/catalog/negative/fec-repair-priority-out-of-band.json +1 -1
  127. package/test-vectors/catalog/negative/fec-repair-source-symbols-without-scope.json +1 -1
  128. package/test-vectors/catalog/negative/multicast-endpoint-relay-address.json +39 -0
  129. package/test-vectors/catalog/negative/multicast-network-source-empty.json +1 -1
  130. package/test-vectors/catalog/negative/raptorq-source-symbols-over-max.json +1 -1
  131. package/test-vectors/catalog/negative/raptorq-unaligned-symbol.json +1 -1
  132. package/test-vectors/catalog/positive/fec-layered-repair.json +2 -2
  133. package/test-vectors/catalog/positive/fec-multicast.json +2 -1
  134. package/test-vectors/catalog/positive/multicast-amt-manual-relay.json +38 -0
  135. package/test-vectors/catalog/positive/multicast-native-ssm-no-network-source.json +2 -1
  136. package/test-vectors/catalog-timing.json +356 -0
  137. package/test-vectors/catalog-validation.json +40 -5
  138. package/test-vectors/delivery-catalog/README.md +11 -0
  139. package/test-vectors/delivery-catalog/negative/missing-block-profile.json +58 -0
  140. package/test-vectors/delivery-catalog/negative/packet-id-signaling.json +59 -0
  141. package/test-vectors/delivery-catalog/negative/retired-k-padded-block-profile.json +59 -0
  142. package/test-vectors/delivery-catalog/positive/dash.json +56 -0
  143. package/test-vectors/delivery-catalog/positive/mmtp-source-only.json +43 -0
  144. package/node_modules/@blockcast/fec-worker/README.md +0 -223
  145. package/node_modules/@blockcast/fec-worker/dist/.build-stamp +0 -0
  146. package/node_modules/@blockcast/fec-worker/dist/block-order.d.ts +0 -38
  147. package/node_modules/@blockcast/fec-worker/dist/block-order.d.ts.map +0 -1
  148. package/node_modules/@blockcast/fec-worker/dist/block-order.js +0 -78
  149. package/node_modules/@blockcast/fec-worker/dist/block-order.js.map +0 -1
  150. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts +0 -93
  151. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts.map +0 -1
  152. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.js +0 -346
  153. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.js.map +0 -1
  154. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.d.ts +0 -380
  155. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.d.ts.map +0 -1
  156. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.js +0 -11
  157. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.js.map +0 -1
  158. package/node_modules/@blockcast/fec-worker/dist/fec-worker.d.ts +0 -14
  159. package/node_modules/@blockcast/fec-worker/dist/fec-worker.d.ts.map +0 -1
  160. package/node_modules/@blockcast/fec-worker/dist/fec-worker.js +0 -859
  161. package/node_modules/@blockcast/fec-worker/dist/fec-worker.js.map +0 -7
  162. package/node_modules/@blockcast/fec-worker/dist/index.d.ts +0 -10
  163. package/node_modules/@blockcast/fec-worker/dist/index.d.ts.map +0 -1
  164. package/node_modules/@blockcast/fec-worker/dist/index.js +0 -8
  165. package/node_modules/@blockcast/fec-worker/dist/index.js.map +0 -1
  166. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.d.ts +0 -20
  167. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.d.ts.map +0 -1
  168. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.js +0 -370
  169. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.js.map +0 -7
  170. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.d.ts +0 -104
  171. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.d.ts.map +0 -1
  172. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.js +0 -181
  173. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.js.map +0 -1
  174. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.d.ts +0 -194
  175. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.d.ts.map +0 -1
  176. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.js +0 -62
  177. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.js.map +0 -1
  178. package/node_modules/@blockcast/fec-worker/dist/transfer.d.ts +0 -9
  179. package/node_modules/@blockcast/fec-worker/dist/transfer.d.ts.map +0 -1
  180. package/node_modules/@blockcast/fec-worker/dist/transfer.js +0 -13
  181. package/node_modules/@blockcast/fec-worker/dist/transfer.js.map +0 -1
  182. package/node_modules/@blockcast/fec-worker/package.json +0 -59
  183. package/node_modules/@blockcast/fec-worker/src/block-order.ts +0 -93
  184. package/node_modules/@blockcast/fec-worker/src/fec-worker-client.ts +0 -392
  185. package/node_modules/@blockcast/fec-worker/src/fec-worker-types.ts +0 -399
  186. package/node_modules/@blockcast/fec-worker/src/fec-worker.ts +0 -1502
  187. package/node_modules/@blockcast/fec-worker/src/index.ts +0 -35
  188. package/node_modules/@blockcast/fec-worker/src/shred-fec-worker.ts +0 -589
  189. package/node_modules/@blockcast/fec-worker/src/shred-worker-client.ts +0 -222
  190. package/node_modules/@blockcast/fec-worker/src/shred-worker-types.ts +0 -209
  191. package/node_modules/@blockcast/fec-worker/src/transfer.ts +0 -12
  192. package/node_modules/@blockcast/mmt-alta-parse/README.md +0 -181
  193. package/node_modules/@blockcast/mmt-alta-parse/dist/.build-stamp +0 -0
  194. package/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts +0 -57
  195. package/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts.map +0 -1
  196. package/node_modules/@blockcast/mmt-alta-parse/dist/index.js +0 -115
  197. package/node_modules/@blockcast/mmt-alta-parse/dist/index.js.map +0 -1
  198. package/node_modules/@blockcast/mmt-alta-parse/package.json +0 -50
  199. package/node_modules/@blockcast/mmt-alta-parse/src/index.ts +0 -136
  200. package/node_modules/@blockcast/mmt-base/README.md +0 -134
  201. package/node_modules/@blockcast/mmt-base/dist/.build-stamp +0 -0
  202. package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts +0 -55
  203. package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts.map +0 -1
  204. package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js +0 -128
  205. package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js.map +0 -1
  206. package/node_modules/@blockcast/mmt-base/dist/index.d.ts +0 -7
  207. package/node_modules/@blockcast/mmt-base/dist/index.d.ts.map +0 -1
  208. package/node_modules/@blockcast/mmt-base/dist/index.js +0 -7
  209. package/node_modules/@blockcast/mmt-base/dist/index.js.map +0 -1
  210. package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +0 -292
  211. package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +0 -1
  212. package/node_modules/@blockcast/mmt-base/dist/mmtp.js +0 -612
  213. package/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +0 -1
  214. package/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts +0 -18
  215. package/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts.map +0 -1
  216. package/node_modules/@blockcast/mmt-base/dist/net-constants.js +0 -18
  217. package/node_modules/@blockcast/mmt-base/dist/net-constants.js.map +0 -1
  218. package/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts +0 -47
  219. package/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts.map +0 -1
  220. package/node_modules/@blockcast/mmt-base/dist/ntp-short.js +0 -54
  221. package/node_modules/@blockcast/mmt-base/dist/ntp-short.js.map +0 -1
  222. package/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts +0 -57
  223. package/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts.map +0 -1
  224. package/node_modules/@blockcast/mmt-base/dist/serial-number.js +0 -98
  225. package/node_modules/@blockcast/mmt-base/dist/serial-number.js.map +0 -1
  226. package/node_modules/@blockcast/mmt-base/dist/signalling.d.ts +0 -312
  227. package/node_modules/@blockcast/mmt-base/dist/signalling.d.ts.map +0 -1
  228. package/node_modules/@blockcast/mmt-base/dist/signalling.js +0 -637
  229. package/node_modules/@blockcast/mmt-base/dist/signalling.js.map +0 -1
  230. package/node_modules/@blockcast/mmt-base/package.json +0 -49
  231. package/node_modules/@blockcast/mmt-base/src/fec-block-profile.ts +0 -145
  232. package/node_modules/@blockcast/mmt-base/src/index.ts +0 -6
  233. package/node_modules/@blockcast/mmt-base/src/mmtp.ts +0 -892
  234. package/node_modules/@blockcast/mmt-base/src/net-constants.ts +0 -17
  235. package/node_modules/@blockcast/mmt-base/src/ntp-short.ts +0 -67
  236. package/node_modules/@blockcast/mmt-base/src/serial-number.ts +0 -107
  237. package/node_modules/@blockcast/mmt-base/src/signalling.ts +0 -862
  238. package/node_modules/@blockcast/mmt-fec/README.md +0 -271
  239. package/node_modules/@blockcast/mmt-fec/dist/.build-stamp +0 -0
  240. package/node_modules/@blockcast/mmt-fec/dist/block-manager.d.ts +0 -82
  241. package/node_modules/@blockcast/mmt-fec/dist/block-manager.d.ts.map +0 -1
  242. package/node_modules/@blockcast/mmt-fec/dist/block-manager.js +0 -296
  243. package/node_modules/@blockcast/mmt-fec/dist/block-manager.js.map +0 -1
  244. package/node_modules/@blockcast/mmt-fec/dist/block-processor.d.ts +0 -106
  245. package/node_modules/@blockcast/mmt-fec/dist/block-processor.d.ts.map +0 -1
  246. package/node_modules/@blockcast/mmt-fec/dist/block-processor.js +0 -288
  247. package/node_modules/@blockcast/mmt-fec/dist/block-processor.js.map +0 -1
  248. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.d.ts +0 -46
  249. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.d.ts.map +0 -1
  250. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.js +0 -96
  251. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.js.map +0 -1
  252. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.d.ts +0 -33
  253. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.d.ts.map +0 -1
  254. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.js +0 -57
  255. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.js.map +0 -1
  256. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.d.ts +0 -205
  257. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.d.ts.map +0 -1
  258. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.js +0 -240
  259. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.js.map +0 -1
  260. package/node_modules/@blockcast/mmt-fec/dist/index.d.ts +0 -28
  261. package/node_modules/@blockcast/mmt-fec/dist/index.d.ts.map +0 -1
  262. package/node_modules/@blockcast/mmt-fec/dist/index.js +0 -28
  263. package/node_modules/@blockcast/mmt-fec/dist/index.js.map +0 -1
  264. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.d.ts +0 -20
  265. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.d.ts.map +0 -1
  266. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.js +0 -28
  267. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.js.map +0 -1
  268. package/node_modules/@blockcast/mmt-fec/dist/oti.d.ts +0 -258
  269. package/node_modules/@blockcast/mmt-fec/dist/oti.d.ts.map +0 -1
  270. package/node_modules/@blockcast/mmt-fec/dist/oti.js +0 -382
  271. package/node_modules/@blockcast/mmt-fec/dist/oti.js.map +0 -1
  272. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.d.ts +0 -41
  273. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.d.ts.map +0 -1
  274. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.js +0 -81
  275. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.js.map +0 -1
  276. package/node_modules/@blockcast/mmt-fec/dist/repair-group.d.ts +0 -82
  277. package/node_modules/@blockcast/mmt-fec/dist/repair-group.d.ts.map +0 -1
  278. package/node_modules/@blockcast/mmt-fec/dist/repair-group.js +0 -170
  279. package/node_modules/@blockcast/mmt-fec/dist/repair-group.js.map +0 -1
  280. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/README.md +0 -181
  281. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/.build-stamp +0 -0
  282. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts +0 -57
  283. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts.map +0 -1
  284. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.js +0 -115
  285. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.js.map +0 -1
  286. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/package.json +0 -50
  287. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/src/index.ts +0 -136
  288. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/README.md +0 -134
  289. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/.build-stamp +0 -0
  290. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts +0 -55
  291. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts.map +0 -1
  292. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js +0 -128
  293. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js.map +0 -1
  294. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.d.ts +0 -7
  295. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.d.ts.map +0 -1
  296. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.js +0 -7
  297. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.js.map +0 -1
  298. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +0 -292
  299. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +0 -1
  300. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.js +0 -612
  301. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +0 -1
  302. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts +0 -18
  303. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts.map +0 -1
  304. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.js +0 -18
  305. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.js.map +0 -1
  306. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts +0 -47
  307. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts.map +0 -1
  308. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.js +0 -54
  309. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.js.map +0 -1
  310. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts +0 -57
  311. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts.map +0 -1
  312. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.js +0 -98
  313. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.js.map +0 -1
  314. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.d.ts +0 -312
  315. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.d.ts.map +0 -1
  316. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.js +0 -637
  317. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.js.map +0 -1
  318. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/package.json +0 -49
  319. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/fec-block-profile.ts +0 -145
  320. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/index.ts +0 -6
  321. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/mmtp.ts +0 -892
  322. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/net-constants.ts +0 -17
  323. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/ntp-short.ts +0 -67
  324. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/serial-number.ts +0 -107
  325. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/signalling.ts +0 -862
  326. package/node_modules/@blockcast/mmt-fec/package.json +0 -79
  327. package/node_modules/@blockcast/mmt-fec/src/block-manager.ts +0 -371
  328. package/node_modules/@blockcast/mmt-fec/src/block-processor.ts +0 -439
  329. package/node_modules/@blockcast/mmt-fec/src/cmaf-recovery.ts +0 -127
  330. package/node_modules/@blockcast/mmt-fec/src/cmaf-symbols.ts +0 -68
  331. package/node_modules/@blockcast/mmt-fec/src/fec-block-mapper.ts +0 -310
  332. package/node_modules/@blockcast/mmt-fec/src/index.ts +0 -94
  333. package/node_modules/@blockcast/mmt-fec/src/loc-symbols.ts +0 -34
  334. package/node_modules/@blockcast/mmt-fec/src/oti.ts +0 -463
  335. package/node_modules/@blockcast/mmt-fec/src/reorder-buffer.ts +0 -101
  336. package/node_modules/@blockcast/mmt-fec/src/repair-group.ts +0 -195
  337. package/node_modules/@blockcast/mmt-manifest-verify/dist/.build-stamp +0 -0
  338. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.d.ts +0 -28
  339. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.d.ts.map +0 -1
  340. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.js +0 -128
  341. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.js.map +0 -1
  342. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.d.ts +0 -4
  343. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.d.ts.map +0 -1
  344. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.js +0 -4
  345. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.js.map +0 -1
  346. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.d.ts +0 -37
  347. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.d.ts.map +0 -1
  348. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.js +0 -179
  349. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.js.map +0 -1
  350. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.d.ts +0 -99
  351. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.d.ts.map +0 -1
  352. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.js +0 -492
  353. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.js.map +0 -1
  354. package/node_modules/@blockcast/mmt-manifest-verify/package.json +0 -45
  355. package/node_modules/@blockcast/mmt-manifest-verify/src/admission.ts +0 -171
  356. package/node_modules/@blockcast/mmt-manifest-verify/src/index.ts +0 -3
  357. package/node_modules/@blockcast/mmt-manifest-verify/src/source-auth.ts +0 -230
  358. package/node_modules/@blockcast/mmt-manifest-verify/src/verifier.ts +0 -669
  359. package/node_modules/@noble/hashes/LICENSE +0 -21
  360. package/node_modules/@noble/hashes/README.md +0 -521
  361. package/node_modules/@noble/hashes/_assert.d.ts +0 -17
  362. package/node_modules/@noble/hashes/_assert.d.ts.map +0 -1
  363. package/node_modules/@noble/hashes/_assert.js +0 -18
  364. package/node_modules/@noble/hashes/_assert.js.map +0 -1
  365. package/node_modules/@noble/hashes/_blake.d.ts +0 -14
  366. package/node_modules/@noble/hashes/_blake.d.ts.map +0 -1
  367. package/node_modules/@noble/hashes/_blake.js +0 -50
  368. package/node_modules/@noble/hashes/_blake.js.map +0 -1
  369. package/node_modules/@noble/hashes/_md.d.ts +0 -51
  370. package/node_modules/@noble/hashes/_md.d.ts.map +0 -1
  371. package/node_modules/@noble/hashes/_md.js +0 -162
  372. package/node_modules/@noble/hashes/_md.js.map +0 -1
  373. package/node_modules/@noble/hashes/_u64.d.ts +0 -55
  374. package/node_modules/@noble/hashes/_u64.d.ts.map +0 -1
  375. package/node_modules/@noble/hashes/_u64.js +0 -90
  376. package/node_modules/@noble/hashes/_u64.js.map +0 -1
  377. package/node_modules/@noble/hashes/argon2.d.ts +0 -32
  378. package/node_modules/@noble/hashes/argon2.d.ts.map +0 -1
  379. package/node_modules/@noble/hashes/argon2.js +0 -401
  380. package/node_modules/@noble/hashes/argon2.js.map +0 -1
  381. package/node_modules/@noble/hashes/blake1.d.ts +0 -106
  382. package/node_modules/@noble/hashes/blake1.d.ts.map +0 -1
  383. package/node_modules/@noble/hashes/blake1.js +0 -459
  384. package/node_modules/@noble/hashes/blake1.js.map +0 -1
  385. package/node_modules/@noble/hashes/blake2.d.ts +0 -116
  386. package/node_modules/@noble/hashes/blake2.d.ts.map +0 -1
  387. package/node_modules/@noble/hashes/blake2.js +0 -420
  388. package/node_modules/@noble/hashes/blake2.js.map +0 -1
  389. package/node_modules/@noble/hashes/blake2b.d.ts +0 -11
  390. package/node_modules/@noble/hashes/blake2b.d.ts.map +0 -1
  391. package/node_modules/@noble/hashes/blake2b.js +0 -14
  392. package/node_modules/@noble/hashes/blake2b.js.map +0 -1
  393. package/node_modules/@noble/hashes/blake2s.d.ts +0 -20
  394. package/node_modules/@noble/hashes/blake2s.d.ts.map +0 -1
  395. package/node_modules/@noble/hashes/blake2s.js +0 -24
  396. package/node_modules/@noble/hashes/blake2s.js.map +0 -1
  397. package/node_modules/@noble/hashes/blake3.d.ts +0 -54
  398. package/node_modules/@noble/hashes/blake3.d.ts.map +0 -1
  399. package/node_modules/@noble/hashes/blake3.js +0 -255
  400. package/node_modules/@noble/hashes/blake3.js.map +0 -1
  401. package/node_modules/@noble/hashes/crypto.d.ts +0 -2
  402. package/node_modules/@noble/hashes/crypto.d.ts.map +0 -1
  403. package/node_modules/@noble/hashes/crypto.js +0 -5
  404. package/node_modules/@noble/hashes/crypto.js.map +0 -1
  405. package/node_modules/@noble/hashes/cryptoNode.d.ts +0 -2
  406. package/node_modules/@noble/hashes/cryptoNode.d.ts.map +0 -1
  407. package/node_modules/@noble/hashes/cryptoNode.js +0 -18
  408. package/node_modules/@noble/hashes/cryptoNode.js.map +0 -1
  409. package/node_modules/@noble/hashes/eskdf.d.ts +0 -47
  410. package/node_modules/@noble/hashes/eskdf.d.ts.map +0 -1
  411. package/node_modules/@noble/hashes/eskdf.js +0 -166
  412. package/node_modules/@noble/hashes/eskdf.js.map +0 -1
  413. package/node_modules/@noble/hashes/esm/_assert.d.ts +0 -17
  414. package/node_modules/@noble/hashes/esm/_assert.d.ts.map +0 -1
  415. package/node_modules/@noble/hashes/esm/_assert.js +0 -15
  416. package/node_modules/@noble/hashes/esm/_assert.js.map +0 -1
  417. package/node_modules/@noble/hashes/esm/_blake.d.ts +0 -14
  418. package/node_modules/@noble/hashes/esm/_blake.d.ts.map +0 -1
  419. package/node_modules/@noble/hashes/esm/_blake.js +0 -45
  420. package/node_modules/@noble/hashes/esm/_blake.js.map +0 -1
  421. package/node_modules/@noble/hashes/esm/_md.d.ts +0 -51
  422. package/node_modules/@noble/hashes/esm/_md.d.ts.map +0 -1
  423. package/node_modules/@noble/hashes/esm/_md.js +0 -155
  424. package/node_modules/@noble/hashes/esm/_md.js.map +0 -1
  425. package/node_modules/@noble/hashes/esm/_u64.d.ts +0 -55
  426. package/node_modules/@noble/hashes/esm/_u64.d.ts.map +0 -1
  427. package/node_modules/@noble/hashes/esm/_u64.js +0 -67
  428. package/node_modules/@noble/hashes/esm/_u64.js.map +0 -1
  429. package/node_modules/@noble/hashes/esm/argon2.d.ts +0 -32
  430. package/node_modules/@noble/hashes/esm/argon2.d.ts.map +0 -1
  431. package/node_modules/@noble/hashes/esm/argon2.js +0 -392
  432. package/node_modules/@noble/hashes/esm/argon2.js.map +0 -1
  433. package/node_modules/@noble/hashes/esm/blake1.d.ts +0 -106
  434. package/node_modules/@noble/hashes/esm/blake1.d.ts.map +0 -1
  435. package/node_modules/@noble/hashes/esm/blake1.js +0 -452
  436. package/node_modules/@noble/hashes/esm/blake1.js.map +0 -1
  437. package/node_modules/@noble/hashes/esm/blake2.d.ts +0 -116
  438. package/node_modules/@noble/hashes/esm/blake2.d.ts.map +0 -1
  439. package/node_modules/@noble/hashes/esm/blake2.js +0 -413
  440. package/node_modules/@noble/hashes/esm/blake2.js.map +0 -1
  441. package/node_modules/@noble/hashes/esm/blake2b.d.ts +0 -11
  442. package/node_modules/@noble/hashes/esm/blake2b.d.ts.map +0 -1
  443. package/node_modules/@noble/hashes/esm/blake2b.js +0 -11
  444. package/node_modules/@noble/hashes/esm/blake2b.js.map +0 -1
  445. package/node_modules/@noble/hashes/esm/blake2s.d.ts +0 -20
  446. package/node_modules/@noble/hashes/esm/blake2s.d.ts.map +0 -1
  447. package/node_modules/@noble/hashes/esm/blake2s.js +0 -21
  448. package/node_modules/@noble/hashes/esm/blake2s.js.map +0 -1
  449. package/node_modules/@noble/hashes/esm/blake3.d.ts +0 -54
  450. package/node_modules/@noble/hashes/esm/blake3.d.ts.map +0 -1
  451. package/node_modules/@noble/hashes/esm/blake3.js +0 -251
  452. package/node_modules/@noble/hashes/esm/blake3.js.map +0 -1
  453. package/node_modules/@noble/hashes/esm/crypto.d.ts +0 -2
  454. package/node_modules/@noble/hashes/esm/crypto.d.ts.map +0 -1
  455. package/node_modules/@noble/hashes/esm/crypto.js +0 -2
  456. package/node_modules/@noble/hashes/esm/crypto.js.map +0 -1
  457. package/node_modules/@noble/hashes/esm/cryptoNode.d.ts +0 -2
  458. package/node_modules/@noble/hashes/esm/cryptoNode.d.ts.map +0 -1
  459. package/node_modules/@noble/hashes/esm/cryptoNode.js +0 -15
  460. package/node_modules/@noble/hashes/esm/cryptoNode.js.map +0 -1
  461. package/node_modules/@noble/hashes/esm/eskdf.d.ts +0 -47
  462. package/node_modules/@noble/hashes/esm/eskdf.d.ts.map +0 -1
  463. package/node_modules/@noble/hashes/esm/eskdf.js +0 -160
  464. package/node_modules/@noble/hashes/esm/eskdf.js.map +0 -1
  465. package/node_modules/@noble/hashes/esm/hkdf.d.ts +0 -36
  466. package/node_modules/@noble/hashes/esm/hkdf.d.ts.map +0 -1
  467. package/node_modules/@noble/hashes/esm/hkdf.js +0 -82
  468. package/node_modules/@noble/hashes/esm/hkdf.js.map +0 -1
  469. package/node_modules/@noble/hashes/esm/hmac.d.ts +0 -35
  470. package/node_modules/@noble/hashes/esm/hmac.d.ts.map +0 -1
  471. package/node_modules/@noble/hashes/esm/hmac.js +0 -86
  472. package/node_modules/@noble/hashes/esm/hmac.js.map +0 -1
  473. package/node_modules/@noble/hashes/esm/index.d.ts +0 -2
  474. package/node_modules/@noble/hashes/esm/index.d.ts.map +0 -1
  475. package/node_modules/@noble/hashes/esm/index.js +0 -33
  476. package/node_modules/@noble/hashes/esm/index.js.map +0 -1
  477. package/node_modules/@noble/hashes/esm/legacy.d.ts +0 -71
  478. package/node_modules/@noble/hashes/esm/legacy.d.ts.map +0 -1
  479. package/node_modules/@noble/hashes/esm/legacy.js +0 -281
  480. package/node_modules/@noble/hashes/esm/legacy.js.map +0 -1
  481. package/node_modules/@noble/hashes/esm/package.json +0 -10
  482. package/node_modules/@noble/hashes/esm/pbkdf2.d.ts +0 -23
  483. package/node_modules/@noble/hashes/esm/pbkdf2.d.ts.map +0 -1
  484. package/node_modules/@noble/hashes/esm/pbkdf2.js +0 -97
  485. package/node_modules/@noble/hashes/esm/pbkdf2.js.map +0 -1
  486. package/node_modules/@noble/hashes/esm/ripemd160.d.ts +0 -13
  487. package/node_modules/@noble/hashes/esm/ripemd160.d.ts.map +0 -1
  488. package/node_modules/@noble/hashes/esm/ripemd160.js +0 -13
  489. package/node_modules/@noble/hashes/esm/ripemd160.js.map +0 -1
  490. package/node_modules/@noble/hashes/esm/scrypt.d.ts +0 -34
  491. package/node_modules/@noble/hashes/esm/scrypt.d.ts.map +0 -1
  492. package/node_modules/@noble/hashes/esm/scrypt.js +0 -228
  493. package/node_modules/@noble/hashes/esm/scrypt.js.map +0 -1
  494. package/node_modules/@noble/hashes/esm/sha1.d.ts +0 -11
  495. package/node_modules/@noble/hashes/esm/sha1.d.ts.map +0 -1
  496. package/node_modules/@noble/hashes/esm/sha1.js +0 -11
  497. package/node_modules/@noble/hashes/esm/sha1.js.map +0 -1
  498. package/node_modules/@noble/hashes/esm/sha2.d.ts +0 -159
  499. package/node_modules/@noble/hashes/esm/sha2.d.ts.map +0 -1
  500. package/node_modules/@noble/hashes/esm/sha2.js +0 -375
  501. package/node_modules/@noble/hashes/esm/sha2.js.map +0 -1
  502. package/node_modules/@noble/hashes/esm/sha256.d.ts +0 -20
  503. package/node_modules/@noble/hashes/esm/sha256.d.ts.map +0 -1
  504. package/node_modules/@noble/hashes/esm/sha256.js +0 -20
  505. package/node_modules/@noble/hashes/esm/sha256.js.map +0 -1
  506. package/node_modules/@noble/hashes/esm/sha3-addons.d.ts +0 -142
  507. package/node_modules/@noble/hashes/esm/sha3-addons.d.ts.map +0 -1
  508. package/node_modules/@noble/hashes/esm/sha3-addons.js +0 -393
  509. package/node_modules/@noble/hashes/esm/sha3-addons.js.map +0 -1
  510. package/node_modules/@noble/hashes/esm/sha3.d.ts +0 -53
  511. package/node_modules/@noble/hashes/esm/sha3.d.ts.map +0 -1
  512. package/node_modules/@noble/hashes/esm/sha3.js +0 -234
  513. package/node_modules/@noble/hashes/esm/sha3.js.map +0 -1
  514. package/node_modules/@noble/hashes/esm/sha512.d.ts +0 -26
  515. package/node_modules/@noble/hashes/esm/sha512.d.ts.map +0 -1
  516. package/node_modules/@noble/hashes/esm/sha512.js +0 -26
  517. package/node_modules/@noble/hashes/esm/sha512.js.map +0 -1
  518. package/node_modules/@noble/hashes/esm/utils.d.ts +0 -161
  519. package/node_modules/@noble/hashes/esm/utils.d.ts.map +0 -1
  520. package/node_modules/@noble/hashes/esm/utils.js +0 -281
  521. package/node_modules/@noble/hashes/esm/utils.js.map +0 -1
  522. package/node_modules/@noble/hashes/hkdf.d.ts +0 -36
  523. package/node_modules/@noble/hashes/hkdf.d.ts.map +0 -1
  524. package/node_modules/@noble/hashes/hkdf.js +0 -88
  525. package/node_modules/@noble/hashes/hkdf.js.map +0 -1
  526. package/node_modules/@noble/hashes/hmac.d.ts +0 -35
  527. package/node_modules/@noble/hashes/hmac.d.ts.map +0 -1
  528. package/node_modules/@noble/hashes/hmac.js +0 -91
  529. package/node_modules/@noble/hashes/hmac.js.map +0 -1
  530. package/node_modules/@noble/hashes/index.d.ts +0 -1
  531. package/node_modules/@noble/hashes/index.d.ts.map +0 -1
  532. package/node_modules/@noble/hashes/index.js +0 -33
  533. package/node_modules/@noble/hashes/index.js.map +0 -1
  534. package/node_modules/@noble/hashes/legacy.d.ts +0 -71
  535. package/node_modules/@noble/hashes/legacy.d.ts.map +0 -1
  536. package/node_modules/@noble/hashes/legacy.js +0 -287
  537. package/node_modules/@noble/hashes/legacy.js.map +0 -1
  538. package/node_modules/@noble/hashes/package.json +0 -266
  539. package/node_modules/@noble/hashes/pbkdf2.d.ts +0 -23
  540. package/node_modules/@noble/hashes/pbkdf2.d.ts.map +0 -1
  541. package/node_modules/@noble/hashes/pbkdf2.js +0 -101
  542. package/node_modules/@noble/hashes/pbkdf2.js.map +0 -1
  543. package/node_modules/@noble/hashes/ripemd160.d.ts +0 -13
  544. package/node_modules/@noble/hashes/ripemd160.d.ts.map +0 -1
  545. package/node_modules/@noble/hashes/ripemd160.js +0 -16
  546. package/node_modules/@noble/hashes/ripemd160.js.map +0 -1
  547. package/node_modules/@noble/hashes/scrypt.d.ts +0 -34
  548. package/node_modules/@noble/hashes/scrypt.d.ts.map +0 -1
  549. package/node_modules/@noble/hashes/scrypt.js +0 -232
  550. package/node_modules/@noble/hashes/scrypt.js.map +0 -1
  551. package/node_modules/@noble/hashes/sha1.d.ts +0 -11
  552. package/node_modules/@noble/hashes/sha1.d.ts.map +0 -1
  553. package/node_modules/@noble/hashes/sha1.js +0 -14
  554. package/node_modules/@noble/hashes/sha1.js.map +0 -1
  555. package/node_modules/@noble/hashes/sha2.d.ts +0 -159
  556. package/node_modules/@noble/hashes/sha2.d.ts.map +0 -1
  557. package/node_modules/@noble/hashes/sha2.js +0 -384
  558. package/node_modules/@noble/hashes/sha2.js.map +0 -1
  559. package/node_modules/@noble/hashes/sha256.d.ts +0 -20
  560. package/node_modules/@noble/hashes/sha256.d.ts.map +0 -1
  561. package/node_modules/@noble/hashes/sha256.js +0 -23
  562. package/node_modules/@noble/hashes/sha256.js.map +0 -1
  563. package/node_modules/@noble/hashes/sha3-addons.d.ts +0 -142
  564. package/node_modules/@noble/hashes/sha3-addons.d.ts.map +0 -1
  565. package/node_modules/@noble/hashes/sha3-addons.js +0 -402
  566. package/node_modules/@noble/hashes/sha3-addons.js.map +0 -1
  567. package/node_modules/@noble/hashes/sha3.d.ts +0 -53
  568. package/node_modules/@noble/hashes/sha3.d.ts.map +0 -1
  569. package/node_modules/@noble/hashes/sha3.js +0 -239
  570. package/node_modules/@noble/hashes/sha3.js.map +0 -1
  571. package/node_modules/@noble/hashes/sha512.d.ts +0 -26
  572. package/node_modules/@noble/hashes/sha512.d.ts.map +0 -1
  573. package/node_modules/@noble/hashes/sha512.js +0 -29
  574. package/node_modules/@noble/hashes/sha512.js.map +0 -1
  575. package/node_modules/@noble/hashes/src/_assert.ts +0 -22
  576. package/node_modules/@noble/hashes/src/_blake.ts +0 -50
  577. package/node_modules/@noble/hashes/src/_md.ts +0 -176
  578. package/node_modules/@noble/hashes/src/_u64.ts +0 -91
  579. package/node_modules/@noble/hashes/src/argon2.ts +0 -497
  580. package/node_modules/@noble/hashes/src/blake1.ts +0 -534
  581. package/node_modules/@noble/hashes/src/blake2.ts +0 -486
  582. package/node_modules/@noble/hashes/src/blake2b.ts +0 -10
  583. package/node_modules/@noble/hashes/src/blake2s.ts +0 -20
  584. package/node_modules/@noble/hashes/src/blake3.ts +0 -272
  585. package/node_modules/@noble/hashes/src/crypto.ts +0 -9
  586. package/node_modules/@noble/hashes/src/cryptoNode.ts +0 -15
  587. package/node_modules/@noble/hashes/src/eskdf.ts +0 -187
  588. package/node_modules/@noble/hashes/src/hkdf.ts +0 -88
  589. package/node_modules/@noble/hashes/src/hmac.ts +0 -94
  590. package/node_modules/@noble/hashes/src/index.ts +0 -31
  591. package/node_modules/@noble/hashes/src/legacy.ts +0 -293
  592. package/node_modules/@noble/hashes/src/pbkdf2.ts +0 -122
  593. package/node_modules/@noble/hashes/src/ripemd160.ts +0 -12
  594. package/node_modules/@noble/hashes/src/scrypt.ts +0 -257
  595. package/node_modules/@noble/hashes/src/sha1.ts +0 -10
  596. package/node_modules/@noble/hashes/src/sha2.ts +0 -402
  597. package/node_modules/@noble/hashes/src/sha256.ts +0 -24
  598. package/node_modules/@noble/hashes/src/sha3-addons.ts +0 -499
  599. package/node_modules/@noble/hashes/src/sha3.ts +0 -258
  600. package/node_modules/@noble/hashes/src/sha512.ts +0 -34
  601. package/node_modules/@noble/hashes/src/utils.ts +0 -395
  602. package/node_modules/@noble/hashes/utils.d.ts +0 -161
  603. package/node_modules/@noble/hashes/utils.d.ts.map +0 -1
  604. package/node_modules/@noble/hashes/utils.js +0 -313
  605. package/node_modules/@noble/hashes/utils.js.map +0 -1
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../mmt-base/src/mmtp.ts", "../../mmt-base/src/ntp-short.ts", "../../mmt-base/src/fec-block-profile.ts", "../node_modules/@blockcast/mmt-transport/src/types.ts", "../node_modules/@blockcast/mmt-base/src/mmtp.ts", "../node_modules/@blockcast/mmt-base/src/net-constants.ts", "../node_modules/@blockcast/mmt-base/src/ntp-short.ts", "../node_modules/@blockcast/mmt-base/src/fec-block-profile.ts", "../node_modules/@blockcast/mmt-transport/src/driad-discovery.ts", "../node_modules/@blockcast/mmt-transport/src/multicast-tier.ts", "../node_modules/@blockcast/mmt-transport/src/abr-controller.ts", "../node_modules/@blockcast/mmt-transport/src/multicast-orchestrator.ts", "../node_modules/@blockcast/mmt-transport/src/endpoint-config.ts", "../node_modules/@blockcast/mmt-transport/src/packet-batch.ts", "../node_modules/@blockcast/mmt-transport/src/multicast-subscription-pool.ts", "../node_modules/@blockcast/mmt-transport/src/transport-manager.ts", "../node_modules/@blockcast/mmt-transport/src/moqt-priority.ts", "../node_modules/@blockcast/mmt-transport/src/amt-gateway.ts", "../src/delivery-catalog.ts"],
4
+ "sourcesContent": ["/**\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\n// Legacy detached per-packet authenticator suffix `[0xf0a1][len][auth]`. Its\n// producer was removed (BLO-16483) and nothing verifies it any more; provenance\n// is the signed per-group manifest. Receivers still skip an exact suffix so a\n// stale producer degrades to unauthenticated playback instead of dropped\n// packets. ponytail: hard-cut to reject once no deployed producer can emit it.\nconst LEGACY_TRAILER_EXT_TYPE = 0xf0a1\nconst LEGACY_TRAILER_HEADER_SIZE = 4\n\nfunction isEmptyOrExactLegacyTrailer(payload: Uint8Array, start: number, end: number): boolean {\n if (start === end) return true\n if (start < 0 || end > payload.length || start + LEGACY_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) !== LEGACY_TRAILER_EXT_TYPE) return false\n\n const authLength = view.getUint16(2, false)\n return start + LEGACY_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 isEmptyOrExactLegacyTrailer(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 || !isEmptyOrExactLegacyTrailer(payload, declaredEnd, fecStart)) {\n return null\n }\n return { hasFecId: true }\n }\n if (fecType !== 0) return null\n\n if (isEmptyOrExactLegacyTrailer(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. The legacy\n * authenticator suffix and the Source FEC trailer 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 legacy authenticator 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/** 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", "/**\n * @blockcast/transport - Type Definitions\n *\n * Shared interfaces for unified transport (WebTransport, SSM, AMT, DRIAD) and FEC.\n */\n\n/** Native IP-multicast request for the window.multicast gateway. */\nexport interface WindowMulticastSubscribeConfigBytes {\n\tgroup: string;\n\tport: number;\n\tsource?: string;\n\tconsume: \"bytes\";\n\torigin?: \"ip-multicast\";\n\t/** MMTP packet IDs to forward before data enters the page transport queue. */\n\tpacketIds?: readonly number[];\n\t/** Catalog track identities corresponding to the selected packet IDs. */\n\ttracks?: ReadonlyArray<{ name: string; packetId: number }>;\n}\n\n/** AMT relay request for the window.multicast gateway. */\nexport interface WindowMulticastSubscribeConfigAmt {\n\tgroup: string;\n\tport: number;\n\tsource?: string;\n\tconsume: \"bytes\";\n\torigin: \"amt\";\n\trelay: string;\n\trelayPort: number;\n\t/** MMTP packet IDs to forward before data enters the page transport queue. */\n\tpacketIds?: readonly number[];\n}\n\n/** Canonical window.multicast config for byte subscriptions. */\nexport type WindowMulticastSubscribeConfig =\n\t| WindowMulticastSubscribeConfigBytes\n\t| WindowMulticastSubscribeConfigAmt;\n\nexport type WindowMulticastSubscriptionState = \"connecting\" | \"connected\" | \"reconnecting\" | \"closed\";\n\nexport interface WindowMulticastSubscriptionEventMap {\n\tbytes: CustomEvent<ArrayBuffer>;\n\t/**\n\t * Provider extension, not part of the frozen v1 contract (BLO-36599): every\n\t * packet the provider decoded in one dispatch batch, in arrival order. Each\n\t * element is exactly what one `bytes` event would have carried. Emitted\n\t * only by a subscription whose `eventTypes` lists `\"packets\"`; see\n\t * {@link subscriptionDeliversPacketBatches}. A consumer listens to exactly\n\t * one of `bytes` and `packets`, because both carry the same packets.\n\t */\n\tpackets: CustomEvent<readonly ArrayBuffer[]>;\n\tstate: CustomEvent<WindowMulticastSubscriptionState>;\n\terror: CustomEvent<unknown>;\n}\n\n/**\n * How a window.multicast subscription hands packets to the page.\n *\n * - `\"datagram\"`: packets are independent datagrams; a pause in arrivals may\n * mean loss.\n * - `\"ordered-relay\"`: the provider relays one receiving socket over a\n * reliable, ordered stream (for example a BCDS bridge WebTransport uni\n * stream). The relay may drop packets (a bounded queue discarding its\n * oldest on overflow) but never reorders them: what arrives is a\n * subsequence of the socket's receive order, so a pause in arrivals\n * carries no loss information.\n *\n * A provider declares `\"ordered-relay\"` only when every packet of the\n * subscription travels that one ordered stream for the life of the\n * subscription. Absent means no ordering promise, which consumers treat as\n * `\"datagram\"`.\n */\nexport type WindowMulticastDelivery = \"datagram\" | \"ordered-relay\";\n\n/** window.multicast subscription returned by the IWA gateway surface. */\nexport interface WindowMulticastSubscription {\n\treadonly id?: string;\n\treadonly state?: WindowMulticastSubscriptionState;\n\t/** How this subscription delivers packets; see WindowMulticastDelivery. */\n\treadonly delivery?: WindowMulticastDelivery;\n\t/**\n\t * Events this subscription emits, when the provider advertises them. A\n\t * provider without the field emits the v1 events only (`bytes`, `state`,\n\t * `error`).\n\t */\n\treadonly eventTypes?: readonly string[];\n\taddEventListener<K extends keyof WindowMulticastSubscriptionEventMap>(\n\t\ttype: K,\n\t\tlistener: (ev: WindowMulticastSubscriptionEventMap[K]) => void,\n\t\toptions?: AddEventListenerOptions | boolean,\n\t): void;\n\tremoveEventListener<K extends keyof WindowMulticastSubscriptionEventMap>(\n\t\ttype: K,\n\t\tlistener: (ev: WindowMulticastSubscriptionEventMap[K]) => void,\n\t\toptions?: EventListenerOptions | boolean,\n\t): void;\n\tunsubscribe: () => Promise<void> | void;\n}\n\nexport interface WindowMulticastCapabilities {\n\treadonly consumeModes?: ReadonlyArray<\"bytes\" | string>;\n}\n\n/** Lowercase window.multicast gateway surface. */\nexport interface WindowMulticastGateway {\n\tsubscribe?: (config: WindowMulticastSubscribeConfig) => Promise<WindowMulticastSubscription>;\n\tcapabilities?: WindowMulticastCapabilities | (() => Promise<WindowMulticastCapabilities>);\n}\n\ndeclare global {\n\tinterface Window {\n\t\tmulticast?: WindowMulticastGateway;\n\t\tUDPSocket?: unknown;\n\t}\n}\n\n/** Transport configuration for multicast subscriptions */\nexport interface TransportConfig {\n\t/** Multicast group address (232.x.x.x for SSM) */\n\tgroup: string;\n\t/** Source address for SSM filtering */\n\tsource: string;\n\t/** UDP port */\n\tport: number;\n\t/** Stream type */\n\tstreamType: \"video\" | \"audio\";\n\t/** Optional AMT relay host for unicast tunneling */\n\tamtRelay?: string;\n\t/** Optional AMT relay port. */\n\tamtRelayPort?: number;\n\t/** Browser gateway transport preference. */\n\tmode?: \"auto\" | \"native\" | \"tunnel\";\n\t/** DRIAD discovery enabled */\n\tdriadEnabled?: boolean;\n}\n\n/** What `TransportManager.connect()` needs beyond the subscription itself. */\nexport interface TransportConnectConfig extends TransportConfig {\n\t/**\n\t * The stream's renditions the ABR controller adapts over, e.g. its catalog\n\t * video tracks. Required: there is no built-in ladder. `connect()` checks\n\t * it (`validateQualityLadder`) and `initialQuality` before tearing anything\n\t * down; a config that fails is refused through the error callback and\n\t * `connect()` resolves `false`, leaving a live subscription untouched.\n\t * `catalogQualityLadder(catalog)` builds it from a catalog.\n\t */\n\tabrLadder: readonly QualityLevel[];\n\t/**\n\t * Ladder rendition to start on, matched by `name` against `abrLadder`; the\n\t * lowest-bitrate rung when omitted. A name `abrLadder` does not carry is\n\t * not a fallback to that rung: `TransportManager.connect()` refuses the\n\t * config (error callback, resolves `false`) before tearing anything down.\n\t */\n\tinitialQuality?: QualityLevel;\n}\n\nexport interface QualityLevel {\n\tname: string;\n\t/** Bits per second: what the ABR controller ranks and costs a rendition by. */\n\tbitrate: number;\n\t/**\n\t * Dimensions and frame rate as the stream's catalog states them; absent\n\t * when it does not. ABR never reads them, so none is defaulted.\n\t */\n\twidth?: number;\n\theight?: number;\n\tfps?: number;\n}\n\nexport interface TransportCapabilities {\n\tnativeSSM: boolean;\n\tnativeIGMP: boolean;\n\tdirectSockets: boolean;\n\tamt: boolean;\n\tdriad: boolean;\n\tfecDecode: boolean;\n}\n\nexport type TransportMode = \"multicast\" | \"amt\" | \"moq\" | \"disconnected\";\n\nexport interface TransportState {\n\tmode: TransportMode;\n\tconnected: boolean;\n\tgroup?: string;\n\tsource?: string;\n\tamtRelay?: string;\n\tquality?: QualityLevel;\n\tpacketsReceived: number;\n\tbytesReceived: number;\n\tlastPacketTime: number;\n}\n\n/** Transport subscription handle */\nexport interface TransportSubscription {\n\tid: string;\n\tconfig: TransportConfig;\n\tonpacket?: (data: ArrayBuffer) => void;\n\tonerror?: (error: Error) => void;\n\tonqualitychange?: (quality: QualityLevel) => void;\n\tclose: () => void;\n}\n\n/** DRIAD Relay information (RFC 8777) */\nexport interface DRIADRelay {\n\thost: string;\n\tport: number;\n\tpriority: number;\n\tweight: number;\n\tasn: number;\n\trtt?: number;\n}\n\n/** AMT Relay information */\nexport interface AmtRelayInfo {\n\taddress: string;\n\tport: number;\n\tdiscoveryMethod: \"driad\" | \"manual\";\n\tdiscoveredAt: number;\n}\n\n/** Group key for multicast subscriptions */\nexport interface GroupKey {\n\tgroup: string;\n\tport: number;\n\tsource?: string;\n}\n\n/** Packet metadata */\nexport interface PacketMetadata {\n\tsourceAddress: string;\n\tsourcePort: number;\n\ttransport: \"native\" | \"tunnel\" | \"websocket\";\n\ttimestamp: number;\n}\n\n/** Time types (microseconds) */\nexport type Micro = number & { readonly __micro: unique symbol };\n\n/** Sync decision for A/V synchronization */\nexport type SyncDecision = \"render\" | \"hold\" | \"discard\";\n\n// ============================================================\n// Multicast Bridge Types (consolidated from @blockcast/multicast)\n// ============================================================\n\n/** Transport mode for multicast subscription (auto-selection) */\nexport type MulticastTransportMode = \"auto\" | \"native\" | \"tunnel\";\n\n/** Multicast subscription configuration */\nexport interface MulticastConfig {\n\tgroup: string;\n\tport: number;\n\tsource?: string;\n\tmode?: MulticastTransportMode;\n\tonPacket: (data: ArrayBuffer, source: PacketSource) => void;\n\tonStateChange?: (state: SubscriptionState) => void;\n\tonError?: (error: Error) => void;\n\tamtRelay?: string;\n\tamtRelayPort?: number;\n\tnativeFallbackTimeout?: number;\n}\n\n/** Packet source information */\nexport interface PacketSource {\n\taddress: string;\n\tport: number;\n\ttransport: \"native\" | \"tunnel\";\n\trecovered?: boolean;\n}\n\n/** Subscription state */\nexport type SubscriptionState = \"initializing\" | \"joining\" | \"active\" | \"suspended\" | \"closed\" | \"error\";\n\n/** Subscription statistics */\nexport interface SubscriptionStats {\n\tpacketsReceived: number;\n\tbytesReceived: number;\n\tpacketsLost: number;\n\tpacketsRecovered: number;\n\tbitrate: number;\n\tuptime: number;\n}\n\n/** Multicast subscription handle */\nexport interface MulticastSubscriptionHandle {\n\treadonly state: SubscriptionState;\n\treadonly group: string;\n\treadonly source?: string;\n\treadonly port: number;\n\treadonly transport: \"native\" | \"tunnel\";\n\treadonly stats: SubscriptionStats;\n\tclose(): Promise<void>;\n}\n\n/** Main Multicast Bridge API */\nexport interface MulticastBridge {\n\tsubscribe(config: MulticastConfig): Promise<MulticastSubscriptionHandle>;\n\tgetSubscriptions(): Promise<MulticastSubscriptionHandle[]>;\n\tisNativeSupported(): Promise<boolean>;\n}\n\n// ============================================================\n// FEC Constants and Utilities per draft-ramadan-moq-fec-00\n// ============================================================\n\n/**\n * Build a conventional repair track path by appending \"repair\" to a source\n * track path.\n *\n * Track names carry no protocol meaning. A repair track's layer, its source\n * and its symbol budget are resolved from the catalog via `resolveRepairLayers`\n * in `@blockcast/mmt-container`, never from a name or a name suffix. This\n * helper is a naming convenience for producers only.\n *\n * @deprecated Naming convenience only. Never classify a track or resolve a\n * repair layer from its path; use `resolveRepairLayers` from\n * `@blockcast/mmt-container`.\n *\n * @param sourceTrack - The source track path (e.g., \"live/video\")\n * @returns The repair track path (e.g., \"live/video/repair\")\n *\n * @example\n * repairTrackPath(\"live/video\") // returns \"live/video/repair\"\n * repairTrackPath(\"broadcast/video/1080p\") // returns \"broadcast/video/1080p/repair\"\n */\nexport function repairTrackPath(sourceTrack: string): string {\n\tif (!sourceTrack || sourceTrack.length === 0) {\n\t\treturn \"repair\";\n\t}\n\treturn `${sourceTrack}/repair`;\n}\n\n/**\n * MoQ FEC Configuration per draft-ramadan-moq-fec-00\n *\n * These parameters are signalled in each track's catalog `fec` block;\n * no FEC_CONFIG control message is sent or parsed.\n */\nexport interface MoqFecConfig {\n\t/** Subscribe ID this config applies to */\n\tsubscribeId: number;\n\t/** Whether FEC is enabled */\n\tenabled: boolean;\n\t/** FEC algorithm (0=none, 1=XOR, 2=RaptorQ) */\n\talgorithm: MoqFecAlgorithm;\n\t/** Number of source symbols per FEC block (K) */\n\tsourceSymbolsPerBlock: number;\n\t/** Number of repair symbols per FEC block (P) */\n\trepairSymbolsPerBlock: number;\n\t/** Interleave depth for burst loss protection */\n\tinterleaveDepth: number;\n\t/** Symbol size in bytes */\n\tsymbolSize: number;\n\t/** RFC 6330 Object Transmission Information (12 bytes) - only for RaptorQ */\n\toti?: Uint8Array;\n}\n\n/**\n * FEC algorithm identifier per draft-ramadan-moq-fec-00\n */\nexport enum MoqFecAlgorithm {\n\t/** No FEC (disabled) */\n\tNone = 0,\n\t/** Simple XOR parity */\n\tXor = 1,\n\t/** RaptorQ (RFC 6330) - recommended for burst loss */\n\tRaptorQ = 2,\n}\n\n/**\n * Repair object header per draft-ramadan-moq-fec-00 Section 6.1\n *\n * Each object on a repair track contains repair symbols for one source block.\n * The object payload begins with this header, followed by concatenated repair symbols.\n */\nexport interface RepairObjectHeader {\n\t/** Source block number being protected (matches group_id of source objects) */\n\tsourceBlockNumber: number;\n\t/** First Repair Symbol ESI (Encoding Symbol ID) - for RaptorQ, repair ESIs >= K */\n\tfirstRepairEsi: number;\n\t/** Number of repair symbols in this object */\n\tnumRepairSymbols: number;\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\n// Legacy detached per-packet authenticator suffix `[0xf0a1][len][auth]`. Its\n// producer was removed (BLO-16483) and nothing verifies it any more; provenance\n// is the signed per-group manifest. Receivers still skip an exact suffix so a\n// stale producer degrades to unauthenticated playback instead of dropped\n// packets. ponytail: hard-cut to reject once no deployed producer can emit it.\nconst LEGACY_TRAILER_EXT_TYPE = 0xf0a1\nconst LEGACY_TRAILER_HEADER_SIZE = 4\n\nfunction isEmptyOrExactLegacyTrailer(payload: Uint8Array, start: number, end: number): boolean {\n if (start === end) return true\n if (start < 0 || end > payload.length || start + LEGACY_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) !== LEGACY_TRAILER_EXT_TYPE) return false\n\n const authLength = view.getUint16(2, false)\n return start + LEGACY_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 isEmptyOrExactLegacyTrailer(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 || !isEmptyOrExactLegacyTrailer(payload, declaredEnd, fecStart)) {\n return null\n }\n return { hasFecId: true }\n }\n if (fecType !== 0) return null\n\n if (isEmptyOrExactLegacyTrailer(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. The legacy\n * authenticator suffix and the Source FEC trailer 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 legacy authenticator 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/** 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 * AMT well-known UDP port (RFC 7450 \u00A74).\n *\n * The single definition for the whole repo. It lives here, in the one package\n * both `@blockcast/mmt-transport` and `@blockcast/mmt-container` already\n * depend on, because the two apply it on opposite sides of a boundary that\n * does not otherwise share code:\n *\n * - container resolves it where `networkSource` semantics live, so a catalog\n * relay that names no port is reachable instead of handing a join a\n * portless relay that fails.\n * - transport applies it when reading a catalog, dialling a gateway, and\n * answering DRIAD \u2014 but deliberately not at every boundary. Which call\n * sites do and do not take the default is transport's business; the\n * reasoning lives beside those call sites, not here.\n */\nexport const DEFAULT_AMT_RELAY_PORT = 2268;\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", "/**\n * @blockcast/transport - DRIAD Discovery (RFC 8777)\n *\n * DRIAD (DNS Reverse IP AMT Discovery) discovers AMT relays based on the\n * **source address**, NOT the multicast group. The source network operator\n * configures DNS records for their source IPs to advertise which AMT relay(s)\n * can tunnel their traffic.\n *\n * Example: For source 69.25.95.10 sending to group 232.0.0.1:\n * Query: 10.95.25.69.amt.in-addr.arpa (source-based, NOT group-based)\n */\n\nimport { DEFAULT_AMT_RELAY_PORT } from \"@blockcast/mmt-base\";\nimport type { DRIADRelay } from \"./types.js\";\n\n/**\n * DRIAD (DNS Reverse IP AMT Discovery) - RFC 8777\n * Discovers AMT relays based on source address via DNS lookups\n */\nexport class DRIADDiscovery {\n\tprivate cache = new Map<string, DRIADRelay[]>();\n\tprivate cacheExpiry = 300_000; // 5 minutes\n\n\t/**\n\t * Discover AMT relays for a multicast source address (RFC 8777)\n\t *\n\t * @param source - The multicast source IP address (NOT the group!)\n\t * @returns Array of discovered AMT relays that can tunnel traffic from this source\n\t */\n\tasync discoverRelays(source: string): Promise<DRIADRelay[]> {\n\t\t// Check cache first\n\t\tconst cached = this.cache.get(source);\n\t\tif (cached) {\n\t\t\treturn cached;\n\t\t}\n\n\t\ttry {\n\t\t\t// Convert source to DRIAD format (reversed octets)\n\t\t\tconst reversedSource = source.split(\".\").reverse().join(\".\");\n\t\t\tconst queryDomain = `${reversedSource}.amt.in-addr.arpa`;\n\n\t\t\tconsole.log(`[DRIAD] Discovering relays for source ${source} via ${queryDomain}`);\n\n\t\t\t// In browser, we use a DRIAD proxy service or DNS-over-HTTPS\n\t\t\tconst relays = await this.queryDRIADProxy(source);\n\n\t\t\tif (relays.length > 0) {\n\t\t\t\tthis.cache.set(source, relays);\n\t\t\t\tsetTimeout(() => this.cache.delete(source), this.cacheExpiry);\n\t\t\t}\n\n\t\t\treturn relays;\n\t\t} catch (error) {\n\t\t\tconsole.warn(`[DRIAD] Discovery failed for source ${source}:`, error);\n\t\t\treturn [];\n\t\t}\n\t}\n\n\t/**\n\t * Query DRIAD proxy service (browser-compatible)\n\t */\n\tprivate async queryDRIADProxy(source: string): Promise<DRIADRelay[]> {\n\t\t// List of known DRIAD proxy endpoints\n\t\tconst proxyEndpoints = [\n\t\t\t\"https://driad.amt.net/api/v1/relays\",\n\t\t\t\"https://multicast-gateway.cloudflare.com/driad\",\n\t\t];\n\n\t\tfor (const endpoint of proxyEndpoints) {\n\t\t\ttry {\n\t\t\t\t// Query by source address per RFC 8777\n\t\t\t\tconst response = await fetch(`${endpoint}?source=${encodeURIComponent(source)}`, {\n\t\t\t\t\tmethod: \"GET\",\n\t\t\t\t\theaders: { Accept: \"application/json\" },\n\t\t\t\t\tsignal: AbortSignal.timeout(5000),\n\t\t\t\t});\n\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = (await response.json()) as { relays: DRIADRelay[] };\n\t\t\t\t\treturn data.relays || [];\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Try next proxy endpoint\n\t\t\t}\n\t\t}\n\n\t\t// Fallback: Try DNS-over-HTTPS query\n\t\tconst dohRelay = await this.queryDNSOverHTTPS(source);\n\t\tif (dohRelay) {\n\t\t\treturn [dohRelay];\n\t\t}\n\n\t\treturn [];\n\t}\n\n\t/**\n\t * Query DNS-over-HTTPS for DRIAD relay based on source address (RFC 8777)\n\t */\n\tprivate async queryDNSOverHTTPS(source: string): Promise<DRIADRelay | null> {\n\t\ttry {\n\t\t\t// Build DRIAD query name from source address (reversed octets)\n\t\t\tconst reversedSource = source.split(\".\").reverse().join(\".\");\n\t\t\tconst queryName = `${reversedSource}.amt.in-addr.arpa`;\n\n\t\t\tconst dohUrl = `https://cloudflare-dns.com/dns-query?name=${queryName}&type=A`;\n\t\t\tconst response = await fetch(dohUrl, {\n\t\t\t\theaders: { Accept: \"application/dns-json\" },\n\t\t\t\tsignal: AbortSignal.timeout(3000),\n\t\t\t});\n\n\t\t\tif (!response.ok) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst result = (await response.json()) as { Answer?: Array<{ data: string }> };\n\n\t\t\t// Extract first A/AAAA record\n\t\t\tif (result.Answer && result.Answer.length > 0) {\n\t\t\t\treturn {\n\t\t\t\t\thost: result.Answer[0].data,\n\t\t\t\t\tport: DEFAULT_AMT_RELAY_PORT,\n\t\t\t\t\tpriority: 10,\n\t\t\t\t\tweight: 100,\n\t\t\t\t\tasn: 0,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn null;\n\t\t} catch (error) {\n\t\t\tconsole.warn(\"[DRIAD] DNS-over-HTTPS query failed:\", error);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Select best relay based on RTT measurements\n\t */\n\tasync selectBestRelay(relays: DRIADRelay[]): Promise<DRIADRelay | null> {\n\t\tif (relays.length === 0) return null;\n\n\t\t// Measure RTT to each relay\n\t\tconst measured = await Promise.all(\n\t\t\trelays.map(async (relay) => {\n\t\t\t\tconst rtt = await this.measureRTT(relay.host, relay.port);\n\t\t\t\treturn { ...relay, rtt };\n\t\t\t}),\n\t\t);\n\n\t\t// Sort by priority, then by RTT\n\t\tmeasured.sort((a, b) => {\n\t\t\tif (a.priority !== b.priority) return a.priority - b.priority;\n\t\t\treturn (a.rtt || Infinity) - (b.rtt || Infinity);\n\t\t});\n\n\t\tconsole.log(`[DRIAD] Selected relay: ${measured[0].host} (RTT: ${measured[0].rtt}ms)`);\n\t\treturn measured[0];\n\t}\n\n\tprivate async measureRTT(host: string, _port: number): Promise<number> {\n\t\tconst start = performance.now();\n\t\ttry {\n\t\t\t// Use HTTP/HTTPS probe since we can't do UDP from browser\n\t\t\tawait fetch(`https://${host}/health`, {\n\t\t\t\tmethod: \"HEAD\",\n\t\t\t\tsignal: AbortSignal.timeout(2000),\n\t\t\t\tmode: \"no-cors\",\n\t\t\t});\n\t\t\treturn performance.now() - start;\n\t\t} catch {\n\t\t\treturn Infinity;\n\t\t}\n\t}\n\n\t/**\n\t * Clear the relay cache\n\t */\n\tclearCache(): void {\n\t\tthis.cache.clear();\n\t}\n\n\t/**\n\t * Get cached relays for a source address\n\t */\n\tgetCached(source: string): DRIADRelay[] | undefined {\n\t\treturn this.cache.get(source);\n\t}\n}\n\n// Export singleton instance\nexport const driadDiscovery = new DRIADDiscovery();\n", "export interface MulticastTierEndpoint {\n\tsourceAddress?: string;\n\tgroupAddress: string;\n\tport: number;\n\ttracks: { name: string; packetId?: number }[];\n\tbandwidth?: number;\n\tprotocol?: string;\n\t/** False when every track on this endpoint is a `fec-repair` track, so the\n\t * endpoint carries repair symbols only and no media. A repair-layer\n\t * endpoint is joined by the loss-driven layer policy, never by the\n\t * bandwidth ladder: selecting one as a tier would leave the receiver with\n\t * no media at all. Set by the catalog policy, which resolves repair-track\n\t * membership from `depends`/`fec.repairTrack` rather than from names.\n\t * Undefined means \"not classified\" and is treated as carrying media, so a\n\t * caller that builds endpoints without the policy is unaffected. */\n\tcarriesMedia?: boolean;\n}\n\nexport function endpointKey(endpoint: MulticastTierEndpoint): string {\n\treturn `${endpoint.sourceAddress ?? \"\"}|${endpoint.groupAddress}|${endpoint.port}`;\n}\n\n/**\n * Whether this endpoint may be selected as a media tier.\n *\n * Every path that picks a tier must apply this, not only `selectMulticastTier`:\n * the orchestrator's headroom up-selection ranks endpoints itself, because it\n * advances one adjacent rung rather than fitting a bandwidth budget. Exported\n * so both rankings share one definition of \"carries media\" instead of\n * repeating the comparison and drifting apart.\n */\nexport function carriesMedia(endpoint: MulticastTierEndpoint): boolean {\n\treturn endpoint.carriesMedia !== false;\n}\n\nexport function selectMulticastTier(\n\tendpoints: readonly MulticastTierEndpoint[],\n\testimate: number | undefined,\n): MulticastTierEndpoint | undefined {\n\t// Repair-only endpoints are never a media tier. Filtering once here covers\n\t// every return path below, including the two early returns and the fallback:\n\t// a ladder that has only repair-layer endpoints returns undefined rather\n\t// than joining one and receiving no media.\n\tconst media = endpoints.filter(carriesMedia);\n\tif (media.length <= 1) return media[0];\n\n\tconst ranked = media\n\t\t.filter((endpoint) => typeof endpoint.bandwidth === \"number\" && endpoint.bandwidth > 0)\n\t\t.sort((a, b) => a.bandwidth! - b.bandwidth!);\n\tif (ranked.length < 2) return media[0];\n\tif (estimate === undefined) return ranked[0];\n\n\tconst budget = estimate * 0.8;\n\tlet selected: MulticastTierEndpoint | undefined;\n\tfor (const endpoint of ranked) {\n\t\tif (endpoint.bandwidth! <= budget) selected = endpoint;\n\t}\n\treturn selected ?? ranked[0];\n}\n", "/**\n * @blockcast/transport - Adaptive Bitrate Controller\n *\n * Progressive adaptive bitrate increase based on network conditions.\n * Monitors received bandwidth and packet loss to adjust quality levels.\n *\n * Per draft-ramadan-moq-fec-00, ABR decisions should use the **effective loss rate**\n * after FEC recovery, not the raw transport-level loss rate. This allows FEC to\n * mask burst losses without unnecessarily downgrading quality.\n */\n\nimport type { QualityLevel } from \"./types.js\";\nimport { selectMulticastTier, type MulticastTierEndpoint } from \"./multicast-tier.js\";\n\n/**\n * FEC statistics for ABR decisions\n */\nexport interface FecStatsForAbr {\n\t/** Raw loss rate before FEC recovery (0.0-1.0) */\n\trawLossRate: number;\n\t/** Effective loss rate after FEC recovery (0.0-1.0) */\n\teffectiveLossRate: number;\n\t/** FEC recovery rate (percentage of lost packets recovered) */\n\trecoveryRate: number;\n\t/** Whether FEC is enabled */\n\tfecEnabled: boolean;\n}\n\n/**\n * A generic 240p..4K ladder, kept as public API only.\n *\n * @deprecated Not a stream's ladder and never a default: the controller\n * adapts over the ladder its caller supplies (`AdaptiveBitrateControllerOptions.ladder`,\n * e.g. the catalog's video renditions). Walking this list is how a player on a\n * 360p/480p/720p catalog logged upgrades to 1080p and 1440p it could never\n * switch to (BLO-35597).\n */\nexport const QUALITY_LEVELS: readonly QualityLevel[] = Object.freeze([\n\t{ name: \"240p\", bitrate: 300_000, width: 426, height: 240, fps: 15 },\n\t{ name: \"360p\", bitrate: 500_000, width: 640, height: 360, fps: 24 },\n\t{ name: \"480p\", bitrate: 1_000_000, width: 854, height: 480, fps: 30 },\n\t{ name: \"720p\", bitrate: 2_500_000, width: 1280, height: 720, fps: 30 },\n\t{ name: \"1080p\", bitrate: 5_000_000, width: 1920, height: 1080, fps: 30 },\n\t{ name: \"1440p\", bitrate: 8_000_000, width: 2560, height: 1440, fps: 30 },\n\t{ name: \"4K\", bitrate: 15_000_000, width: 3840, height: 2160, fps: 30 },\n].map((level) => Object.freeze(level)));\n\n/** What the controller adapts over and where it starts; both from the caller. */\nexport interface AdaptiveBitrateControllerOptions {\n\t/**\n\t * The renditions this stream carries (e.g. the catalog's video tracks), in\n\t * any order: a unique name and a bitrate (bps) each, plus dimensions and\n\t * fps when the catalog states them. Renditions may share a bitrate.\n\t */\n\tladder: readonly QualityLevel[];\n\t/** Name of the ladder rendition presented at construction; the lowest\n\t * rung when omitted. A name that matches no rung is refused. */\n\tinitialQuality?: string;\n}\n\nfunction positiveFinite(value: unknown): value is number {\n\treturn typeof value === \"number\" && Number.isFinite(value) && value > 0;\n}\n\n/**\n * Validate a caller ladder and return it frozen, ascending by bitrate. Throws\n * on an empty ladder, a rung without a name or a positive finite bitrate, a\n * width, height or fps that is present but not positive finite, or a name that\n * appears twice: a name is how `setQuality` and the initial rendition find a\n * rung. A shared bitrate is legal (e.g. 720p30 and 720p60, or two codecs);\n * `Array.prototype.sort` is stable, so equal bitrates keep the caller's order.\n */\nexport function validateQualityLadder(ladder: readonly QualityLevel[]): readonly QualityLevel[] {\n\tif (!Array.isArray(ladder) || ladder.length === 0) {\n\t\tthrow new TypeError(\"[ABR] ladder must list at least one rendition (e.g. the catalog's video tracks)\");\n\t}\n\tconst names = new Set<string>();\n\tfor (const level of ladder) {\n\t\tif (typeof level?.name !== \"string\" || level.name.length === 0) {\n\t\t\tthrow new TypeError(\"[ABR] every ladder rendition needs a name\");\n\t\t}\n\t\tif (!positiveFinite(level.bitrate)) {\n\t\t\tthrow new RangeError(`[ABR] rendition ${level.name}: bitrate must be a positive finite number, got ${String(level.bitrate)}`);\n\t\t}\n\t\tfor (const field of [\"width\", \"height\", \"fps\"] as const) {\n\t\t\tif (level[field] !== undefined && !positiveFinite(level[field])) {\n\t\t\t\tthrow new RangeError(`[ABR] rendition ${level.name}: ${field} must be a positive finite number when stated, got ${String(level[field])}`);\n\t\t\t}\n\t\t}\n\t\tif (names.has(level.name)) throw new RangeError(`[ABR] rendition ${level.name} appears twice in the ladder`);\n\t\tnames.add(level.name);\n\t}\n\treturn Object.freeze(\n\t\t[...ladder].sort((a, b) => a.bitrate - b.bitrate).map((level) => Object.freeze({ ...level })),\n\t);\n}\n\n/**\n * Progressive Adaptive Bitrate Controller\n *\n * Features:\n * - Monitors bandwidth via received bytes over time window\n * - Tracks packet loss rate (raw and effective after FEC)\n * - Uses effective loss rate for ABR decisions per draft-ramadan-moq-fec-00\n * - Progressive quality upgrades (waits for stability)\n * - Fast quality downgrades on congestion\n */\nexport class AdaptiveBitrateController {\n\t/** The caller's renditions, ascending by bitrate (`validateQualityLadder`). */\n\treadonly ladder: readonly QualityLevel[];\n\tprivate currentQualityIndex: number;\n\tprivate history: { timestamp: number; bytesReceived: number; packetsLost: number }[] = [];\n\tprivate readonly historyWindow = 10_000; // 10 second window\n\tprivate readonly upgradeThreshold = 0.8; // 80% of target bitrate sustained\n\tprivate readonly downgradeThreshold = 0.5; // 50% of target bitrate triggers downgrade\n\tprivate readonly stabilityPeriod = 5_000; // Wait 5s before upgrading\n\n\tprivate lastQualityChange = 0;\n\tprivate onQualityChange?: (quality: QualityLevel) => void;\n\n\t// FEC stats for effective loss rate calculation\n\tprivate fecStats: FecStatsForAbr = {\n\t\trawLossRate: 0,\n\t\teffectiveLossRate: 0,\n\t\trecoveryRate: 1,\n\t\tfecEnabled: false,\n\t};\n\n\tconstructor(options: AdaptiveBitrateControllerOptions) {\n\t\tif (!options || typeof options !== \"object\") {\n\t\t\tthrow new TypeError(\"[ABR] AdaptiveBitrateController needs { ladder, initialQuality }\");\n\t\t}\n\t\tthis.ladder = validateQualityLadder(options.ladder);\n\t\tthis.currentQualityIndex =\n\t\t\toptions.initialQuality === undefined ? 0 : this.ladder.findIndex((q) => q.name === options.initialQuality);\n\t\tif (this.currentQualityIndex < 0) {\n\t\t\tthrow new RangeError(`[ABR] initial rendition ${String(options.initialQuality)} is not in the ladder`);\n\t\t}\n\t}\n\n\tsetCallback(callback: (quality: QualityLevel) => void) {\n\t\tthis.onQualityChange = callback;\n\t}\n\n\tgetCurrentQuality(): QualityLevel {\n\t\treturn this.ladder[this.currentQualityIndex];\n\t}\n\n\t/** Selects the multicast (S,G) tier without replacing a player's unicast ABR manager. */\n\tselectMulticastTier(\n\t\tendpoints: readonly MulticastTierEndpoint[],\n\t\testimate: number | undefined = this.getMetrics().measuredBitrate || undefined,\n\t): MulticastTierEndpoint | undefined {\n\t\treturn selectMulticastTier(endpoints, estimate);\n\t}\n\n\t/**\n\t * Record network sample for bitrate adaptation\n\t *\n\t * @param bytesReceived - Bytes received in this sample\n\t * @param packetsLost - Packets lost (raw, before FEC recovery)\n\t */\n\trecordSample(bytesReceived: number, packetsLost: number) {\n\t\tconst now = Date.now();\n\n\t\tthis.history.push({ timestamp: now, bytesReceived, packetsLost });\n\n\t\t// Prune old samples\n\t\tthis.history = this.history.filter((s) => now - s.timestamp < this.historyWindow);\n\n\t\t// Calculate metrics\n\t\tconst { measuredBitrate, lossRate: rawLossRate } = this.calculateMetrics();\n\t\tconst currentQuality = this.getCurrentQuality();\n\n\t\t// Per draft-ramadan-moq-fec-00: Use effective loss rate after FEC for ABR decisions\n\t\t// This allows FEC to mask burst losses without triggering unnecessary downgrades\n\t\tconst effectiveLossRate = this.fecStats.fecEnabled\n\t\t\t? this.fecStats.effectiveLossRate\n\t\t\t: rawLossRate;\n\n\t\t// Decision logic\n\t\tif (now - this.lastQualityChange < this.stabilityPeriod) {\n\t\t\treturn; // Wait for stability\n\t\t}\n\n\t\t// Check for downgrade - use effective loss rate (post-FEC)\n\t\t// Only downgrade if FEC cannot recover the losses\n\t\tif (effectiveLossRate > 0.05 || measuredBitrate < currentQuality.bitrate * this.downgradeThreshold) {\n\t\t\tthis.downgrade();\n\t\t\treturn;\n\t\t}\n\n\t\t// Check for upgrade (progressive increase) - use effective loss rate\n\t\tif (\n\t\t\tmeasuredBitrate > currentQuality.bitrate * this.upgradeThreshold &&\n\t\t\teffectiveLossRate < 0.01 &&\n\t\t\tthis.currentQualityIndex < this.ladder.length - 1\n\t\t) {\n\t\t\tthis.upgrade();\n\t\t}\n\t}\n\n\t/**\n\t * Update FEC statistics for ABR decisions\n\t *\n\t * Per draft-ramadan-moq-fec-00, ABR should use the effective loss rate\n\t * (after FEC recovery) rather than raw transport loss rate.\n\t *\n\t * @param stats - FEC statistics from the FEC decoder\n\t */\n\tsetFecStats(stats: FecStatsForAbr) {\n\t\tthis.fecStats = stats;\n\t}\n\n\t/**\n\t * Get current FEC statistics\n\t */\n\tgetFecStats(): FecStatsForAbr {\n\t\treturn { ...this.fecStats };\n\t}\n\n\tprivate calculateMetrics() {\n\t\tif (this.history.length < 2) {\n\t\t\treturn { measuredBitrate: 0, lossRate: 0 };\n\t\t}\n\n\t\tconst totalBytes = this.history.reduce((sum, s) => sum + s.bytesReceived, 0);\n\t\tconst totalLost = this.history.reduce((sum, s) => sum + s.packetsLost, 0);\n\t\tconst duration = (this.history[this.history.length - 1].timestamp - this.history[0].timestamp) / 1000;\n\n\t\tconst measuredBitrate = duration > 0 ? (totalBytes * 8) / duration : 0;\n\t\tconst lossRate = totalLost / (this.history.length + totalLost);\n\n\t\treturn { measuredBitrate, lossRate };\n\t}\n\n\tprivate upgrade() {\n\t\tif (this.currentQualityIndex >= this.ladder.length - 1) return;\n\n\t\tthis.currentQualityIndex++;\n\t\tthis.lastQualityChange = Date.now();\n\n\t\tconst newQuality = this.getCurrentQuality();\n\t\tconsole.log(`[ABR] Upgrading to ${newQuality.name} (${newQuality.bitrate / 1_000_000}Mbps)`);\n\n\t\tthis.onQualityChange?.(newQuality);\n\t}\n\n\tprivate downgrade() {\n\t\tif (this.currentQualityIndex <= 0) return;\n\n\t\tthis.currentQualityIndex--;\n\t\tthis.lastQualityChange = Date.now();\n\n\t\tconst newQuality = this.getCurrentQuality();\n\t\tconsole.log(`[ABR] Downgrading to ${newQuality.name} (${newQuality.bitrate / 1_000_000}Mbps)`);\n\n\t\tthis.onQualityChange?.(newQuality);\n\t}\n\n\t/**\n\t * Select a ladder rendition: the caller switched to it (a manual override,\n\t * or a switch the caller made). Throws for a name the ladder does not carry.\n\t */\n\tsetQuality(name: string) {\n\t\tconst index = this.ladder.findIndex((q) => q.name === name);\n\t\tif (index < 0) throw new RangeError(`[ABR] rendition ${name} is not in the ladder`);\n\t\tthis.currentQualityIndex = index;\n\t\tthis.lastQualityChange = Date.now();\n\t\tthis.onQualityChange?.(this.getCurrentQuality());\n\t}\n\n\t/**\n\t * Get current metrics including both raw and effective loss rates\n\t */\n\tgetMetrics(): {\n\t\tmeasuredBitrate: number;\n\t\trawLossRate: number;\n\t\teffectiveLossRate: number;\n\t\tfecEnabled: boolean;\n\t\tsampleCount: number;\n\t} {\n\t\tconst { measuredBitrate, lossRate: rawLossRate } = this.calculateMetrics();\n\t\treturn {\n\t\t\tmeasuredBitrate,\n\t\t\trawLossRate,\n\t\t\teffectiveLossRate: this.fecStats.fecEnabled ? this.fecStats.effectiveLossRate : rawLossRate,\n\t\t\tfecEnabled: this.fecStats.fecEnabled,\n\t\t\tsampleCount: this.history.length,\n\t\t};\n\t}\n\n\t/**\n\t * Reset history and start fresh\n\t */\n\treset() {\n\t\tthis.history = [];\n\t\tthis.lastQualityChange = 0;\n\t}\n}\n", "import { compareNtpShort, isNtpShort } from \"@blockcast/mmt-base\";\nimport { carriesMedia, endpointKey, selectMulticastTier, type MulticastTierEndpoint } from \"./multicast-tier.js\";\n\nexport type OrchestratorState = \"stable\" | \"joining\";\nexport type OrchestratorCommand =\n\t| { kind: \"join-target\"; current: MulticastTierEndpoint; target: MulticastTierEndpoint }\n\t| { kind: \"commit-swap\"; target: MulticastTierEndpoint; old: MulticastTierEndpoint }\n\t| { kind: \"abandon-join\"; current: MulticastTierEndpoint; target: MulticastTierEndpoint };\n\nexport interface MulticastTierPacket {\n\tendpointKey: string;\n\tkeyframe: boolean;\n\t/**\n\t * The packet's MMTP header `timestamp`: its UTC sending instant in NTP short\n\t * format (ISO/IEC 23008-1 \u00A79.2.2), which wraps every 2^16 s (~18.2 h). The\n\t * orchestrator orders these only with RFC 1982 serial arithmetic\n\t * (`compareNtpShort`), never with `<` or `Math.max`.\n\t *\n\t * A non-finite value means the host could not read a header; that packet\n\t * neither raises the high-water mark nor commits. Any other value that is\n\t * not NTP short is a host bug and throws a RangeError.\n\t */\n\ttimestamp: number;\n\thasValidCodecConfig: boolean;\n}\n\nexport interface OrchestratorOptions {\n\tendpoints: readonly MulticastTierEndpoint[];\n\tinitialEndpoint?: MulticastTierEndpoint;\n\tadaptive?: boolean;\n\tnow: () => number;\n\tdwellMs?: number;\n\tjoinDeadlineMs?: number;\n}\n\nexport interface GoodputSample { bitrate: number | undefined; }\nexport interface HeadroomSample { healthy: boolean; }\n\nexport const DEFAULT_DWELL_MS = 3_000;\nexport const DEFAULT_JOIN_DEADLINE_MS = 5_000;\n\nexport function goodputFromStats(\n\tprevious: { bytesReceived: number; atMs: number } | undefined,\n\tcurrent: { bytesReceived: number; atMs: number },\n\tminSampleMs = 500,\n): number | undefined {\n\tif (!previous) return undefined;\n\tconst durationMs = current.atMs - previous.atMs;\n\tconst bytes = current.bytesReceived - previous.bytesReceived;\n\tif (durationMs < minSampleMs || bytes <= 0) return undefined;\n\treturn (bytes * 8 * 1000) / durationMs;\n}\n\nexport class MulticastOrchestrator {\n\t#state: OrchestratorState = \"stable\";\n\t#current: MulticastTierEndpoint | undefined;\n\t#target: MulticastTierEndpoint | undefined;\n\t#estimate: number | undefined;\n\t#favored: { key: string; since: number; source: \"goodput\" | \"headroom\" } | undefined;\n\t#joinStartedAt = 0;\n\t/**\n\t * Latest active-tier send time (NTP short), or undefined before the first.\n\t * \"Latest\" is serial order, not the numerically largest value: after the\n\t * field wraps, the largest value is the stale one.\n\t */\n\t#highWater: number | undefined;\n\treadonly #dwellMs: number;\n\treadonly #joinDeadlineMs: number;\n\n\tconstructor(private readonly options: OrchestratorOptions) {\n\t\tthis.#dwellMs = options.dwellMs ?? DEFAULT_DWELL_MS;\n\t\tthis.#joinDeadlineMs = options.joinDeadlineMs ?? DEFAULT_JOIN_DEADLINE_MS;\n\t\tif (!Number.isFinite(this.#dwellMs) || !Number.isFinite(this.#joinDeadlineMs) ||\n\t\t\tthis.#dwellMs <= 0 || this.#joinDeadlineMs <= 0) {\n\t\t\tthrow new Error(\"Multicast dwell and join deadline must be positive\");\n\t\t}\n\t}\n\n\tget state(): OrchestratorState { return this.#state; }\n\tget current(): MulticastTierEndpoint | undefined { return this.#current; }\n\tget target(): MulticastTierEndpoint | undefined { return this.#target; }\n\tget estimate(): number | undefined { return this.#estimate; }\n\n\tstart(): MulticastTierEndpoint | undefined {\n\t\tthis.#current ??= this.options.initialEndpoint ?? selectMulticastTier(this.options.endpoints, undefined);\n\t\treturn this.#current;\n\t}\n\n\tonGoodput(sample: GoodputSample): OrchestratorCommand | undefined {\n\t\tconst bitrate = sample.bitrate;\n\t\tif (!this.#current) return undefined;\n\t\t// Before the adaptive gate: a selected join runs with adaptation off, and a\n\t\t// target that delivers nothing reaches its deadline only on this clock.\n\t\tif (this.#state === \"joining\") return this.poll();\n\t\tif (this.options.adaptive === false) return undefined;\n\t\tif (bitrate === undefined || !Number.isFinite(bitrate)) return undefined;\n\t\tthis.#estimate = bitrate;\n\t\tconst desired = selectMulticastTier(this.options.endpoints, bitrate);\n\t\tif (!desired) return undefined;\n\t\tconst desiredKey = endpointKey(desired);\n\t\tif (desiredKey === endpointKey(this.#current)) {\n\t\t\tif (this.#favored?.source === \"goodput\") this.#favored = undefined;\n\t\t\treturn undefined;\n\t\t}\n\t\tif (typeof desired.bandwidth !== \"number\" || typeof this.#current.bandwidth !== \"number\" ||\n\t\t\tdesired.bandwidth >= this.#current.bandwidth) {\n\t\t\tif (this.#favored?.source === \"goodput\") this.#favored = undefined;\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst now = this.#now();\n\t\tif (!this.#favored || this.#favored.key !== desiredKey || this.#favored.source !== \"goodput\") {\n\t\t\tthis.#favored = { key: desiredKey, since: now, source: \"goodput\" };\n\t\t\treturn undefined;\n\t\t}\n\t\tif (now - this.#favored.since < this.#dwellMs) return undefined;\n\n\t\tthis.#state = \"joining\";\n\t\tthis.#target = desired;\n\t\tthis.#joinStartedAt = now;\n\t\tthis.#favored = undefined;\n\t\treturn { kind: \"join-target\", current: this.#current, target: desired };\n\t}\n\n\tonHeadroom(sample: HeadroomSample): OrchestratorCommand | undefined {\n\t\tif (!this.#current || this.options.adaptive === false || this.#state === \"joining\") return undefined;\n\t\tif (!sample.healthy) {\n\t\t\tif (this.#favored?.source === \"headroom\") this.#favored = undefined;\n\t\t\treturn undefined;\n\t\t}\n\t\tif (this.#favored?.source === \"goodput\") return undefined;\n\n\t\tconst currentBandwidth = this.#current.bandwidth;\n\t\tif (typeof currentBandwidth !== \"number\") return undefined;\n\t\t// Ranked here rather than through selectMulticastTier: up-selection\n\t\t// advances exactly one adjacent rung, where the selector fits a budget to\n\t\t// an estimate. The media filter is shared so a repair-only endpoint above\n\t\t// the current rung is not joined as an upgrade, which would swap the\n\t\t// receiver onto repair symbols and no media.\n\t\tconst desired = this.options.endpoints\n\t\t\t.filter((endpoint) =>\n\t\t\t\tcarriesMedia(endpoint) && typeof endpoint.bandwidth === \"number\" && endpoint.bandwidth > currentBandwidth,\n\t\t\t)\n\t\t\t.sort((a, b) => a.bandwidth! - b.bandwidth!)[0];\n\t\tif (!desired) {\n\t\t\tthis.#favored = undefined;\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst desiredKey = endpointKey(desired);\n\t\tif (desiredKey === endpointKey(this.#current)) {\n\t\t\tthis.#favored = undefined;\n\t\t\treturn undefined;\n\t\t}\n\t\tconst now = this.#now();\n\t\tif (!this.#favored || this.#favored.key !== desiredKey) {\n\t\t\tthis.#favored = { key: desiredKey, since: now, source: \"headroom\" };\n\t\t\treturn undefined;\n\t\t}\n\t\tif (now - this.#favored.since < this.#dwellMs) return undefined;\n\n\t\tthis.#state = \"joining\";\n\t\tthis.#target = desired;\n\t\tthis.#joinStartedAt = now;\n\t\tthis.#favored = undefined;\n\t\treturn { kind: \"join-target\", current: this.#current, target: desired };\n\t}\n\n\t/**\n\t * Join `target` because the viewer selected it, not because goodput or\n\t * headroom ranked it. It runs whether or not the orchestrator adapts, and\n\t * takes the path an adaptive join takes: commit on the target's RAP at or\n\t * beyond the high-water mark, abandon at the join deadline. While it is\n\t * pending nothing re-ranks the ladder; goodput only advances the deadline.\n\t *\n\t * Returns undefined, changing nothing, while another join is pending (cancel\n\t * it first), when `target` is the current tier, or when it carries no media.\n\t */\n\tselect(target: MulticastTierEndpoint): OrchestratorCommand | undefined {\n\t\tif (!this.#current || this.#state === \"joining\" || !carriesMedia(target)) return undefined;\n\t\tif (endpointKey(target) === endpointKey(this.#current)) return undefined;\n\t\tthis.#state = \"joining\";\n\t\tthis.#target = target;\n\t\tthis.#joinStartedAt = this.#now();\n\t\tthis.#favored = undefined;\n\t\treturn { kind: \"join-target\", current: this.#current, target };\n\t}\n\n\t/**\n\t * Feed a packet from the active tier (to advance the high-water mark) or\n\t * from the in-flight target (to find a committable RAP).\n\t *\n\t * Serial order is only the true order while the compared instants are less\n\t * than `NTP_SHORT_HALF_RANGE` (2^15 s, ~9.1 h) apart. Feeding every\n\t * active-tier packet keeps the mark that close to the live stream. A mark\n\t * refreshed only by commits can fall further behind than that, and a\n\t * target RAP may then compare as older, so the join is abandoned.\n\t */\n\tonTierPacket(packet: MulticastTierPacket): OrchestratorCommand | undefined {\n\t\tif (!this.#current) return undefined;\n\t\tconst sentAt = tierPacketSendTime(packet);\n\t\t// An instant exactly half the space from the mark (RFC 1982's undefined\n\t\t// case) is neither later nor earlier, so it leaves the mark alone, as a\n\t\t// reordered earlier packet does.\n\t\tif (packet.endpointKey === endpointKey(this.#current) && sentAt !== undefined &&\n\t\t\t(this.#highWater === undefined || compareNtpShort(sentAt, this.#highWater) === \"after\")) {\n\t\t\tthis.#highWater = sentAt;\n\t\t}\n\t\tif (this.#state !== \"joining\" || !this.#target) return undefined;\n\t\tconst expired = this.poll();\n\t\tif (expired) return expired;\n\t\tif (\n\t\t\tpacket.endpointKey !== endpointKey(this.#target) ||\n\t\t\t!packet.keyframe ||\n\t\t\t!packet.hasValidCodecConfig ||\n\t\t\tsentAt === undefined ||\n\t\t\t!this.#reachesHighWater(sentAt)\n\t\t) return undefined;\n\n\t\tconst command: OrchestratorCommand = {\n\t\t\tkind: \"commit-swap\",\n\t\t\ttarget: this.#target,\n\t\t\told: this.#current,\n\t\t};\n\t\tthis.#highWater = sentAt;\n\t\tthis.#current = this.#target;\n\t\tthis.#target = undefined;\n\t\tthis.#state = \"stable\";\n\t\treturn command;\n\t}\n\n\tpoll(): OrchestratorCommand | undefined {\n\t\tif (this.#state !== \"joining\" || !this.#target || !this.#current) return undefined;\n\t\tif (this.#now() - this.#joinStartedAt < this.#joinDeadlineMs) return undefined;\n\t\treturn this.cancel();\n\t}\n\n\tcancel(): OrchestratorCommand | undefined {\n\t\tif (this.#state !== \"joining\" || !this.#target || !this.#current) return undefined;\n\t\tconst command: OrchestratorCommand = {\n\t\t\tkind: \"abandon-join\",\n\t\t\tcurrent: this.#current,\n\t\t\ttarget: this.#target,\n\t\t};\n\t\tthis.#target = undefined;\n\t\tthis.#state = \"stable\";\n\t\treturn command;\n\t}\n\n\t/**\n\t * Whether a target RAP sent at `sentAt` is at or beyond the active tier's\n\t * high-water mark. A pair exactly half the NTP-short space apart has no\n\t * serial order, so it cannot show that and does not commit.\n\t */\n\t#reachesHighWater(sentAt: number): boolean {\n\t\tif (this.#highWater === undefined) return true;\n\t\tconst order = compareNtpShort(sentAt, this.#highWater);\n\t\treturn order === \"after\" || order === \"equal\";\n\t}\n\n\t#now(): number {\n\t\tconst now = this.options.now();\n\t\tif (!Number.isFinite(now)) throw new Error(\"Multicast orchestrator clock must be finite\");\n\t\treturn now;\n\t}\n}\n\n/** A tier packet's NTP-short send time, or undefined when the host had no header to read. */\nfunction tierPacketSendTime(packet: MulticastTierPacket): number | undefined {\n\tif (!Number.isFinite(packet.timestamp)) return undefined;\n\tif (!isNtpShort(packet.timestamp)) {\n\t\tthrow new RangeError(\n\t\t\t`Multicast tier packet timestamp must be an MMTP NTP-short send time, got ${packet.timestamp}`,\n\t\t);\n\t}\n\treturn packet.timestamp;\n}\n", "import { MMTP_PACKET_ID_MAX, MMTP_SIGNALING_PACKET_ID } from \"@blockcast/mmt-base\";\nimport type {\n\tWindowMulticastSubscribeConfig,\n\tWindowMulticastSubscribeConfigBytes,\n} from \"./types.js\";\n\nexport type MulticastTransportPreference = \"auto\" | \"native\" | \"tunnel\";\n\nexport { DEFAULT_AMT_RELAY_PORT } from \"@blockcast/mmt-base\";\n\nexport interface MulticastEndpointConfig {\n\tgroupAddress: string;\n\tsourceAddress?: string;\n\tport: number;\n\tmode?: MulticastTransportPreference;\n\tamtRelay?: string;\n\tamtRelayPort?: number;\n}\n\n/**\n * Input accepted by the canonical window.multicast request converter.\n *\n * `mode` and the `amtRelay*` fields are internal endpoint-selection state;\n * they are deliberately not emitted on the page-facing request. The\n * resulting `origin` is the only acquisition selector understood by the\n * gateway.\n */\nexport interface WindowMulticastEndpointInput {\n\tgroup: string;\n\tport: number;\n\tsource?: string;\n\tmode?: MulticastTransportPreference;\n\tamtRelay?: string;\n\tamtRelayPort?: number;\n\tpacketIds?: readonly number[];\n\ttracks?: ReadonlyArray<{ name: string; packetId: number }>;\n}\n\n/**\n * Convert internal endpoint-selection state to the canonical page contract.\n *\n * Native is the default and an explicit `native` selection always wins over\n * stale relay metadata. Tunnel/AMT requests must carry the resolved relay\n * and port; there is no implicit relay-port default at this boundary.\n *\n * That rule is deliberate and is NOT the place to fix a relay that names no\n * port: this is the last hop before the page-facing request, so a default\n * applied here would be invisible to every caller and to the join it produces.\n * A catalog relay with no port gets `DEFAULT_AMT_RELAY_PORT` upstream, in the\n * readers that resolve it, and arrives here explicit like any other\n * (BLO-38176).\n */\nexport function toWindowMulticastSubscribeConfig(\n\tinput: WindowMulticastEndpointInput,\n): WindowMulticastSubscribeConfig {\n\tif (input.mode !== undefined && ![\"auto\", \"native\", \"tunnel\"].includes(input.mode)) {\n\t\tthrow new TypeError(`Unsupported multicast mode: ${String(input.mode)}`);\n\t}\n\t// An explicit non-array (the null \"endpoint unusable\" sentinel from\n\t// selectEndpointPacketIdsForTracks) must not degrade into \"no filter\".\n\tif (input.packetIds !== undefined && !Array.isArray(input.packetIds)) {\n\t\tthrow new TypeError(\n\t\t\t`multicast packetIds must be an array, got ${input.packetIds === null ? \"null\" : typeof input.packetIds}`,\n\t\t);\n\t}\n\t// Every join request passes through here, so the track packet_id range is\n\t// enforced here rather than trusted from each caller.\n\tif (input.packetIds?.some(id => !isMulticastTrackPacketId(id)) ||\n\t\tinput.tracks?.some(track => !isMulticastTrackPacketId(track.packetId))) {\n\t\tthrow new RangeError(\n\t\t\t`multicast packetId must be an integer from 1 to ${MMTP_PACKET_ID_MAX}; ` +\n\t\t\t\"packetId 0 is the MMTP signaling flow and is never a track's\",\n\t\t);\n\t}\n\n\tconst base = {\n\t\tgroup: input.group,\n\t\tport: input.port,\n\t\t...(input.source?.trim() ? { source: input.source.trim() } : {}),\n\t\tconsume: \"bytes\" as const,\n\t\t...(input.packetIds?.length ? { packetIds: [...input.packetIds] } : {}),\n\t};\n\n\tconst relay = input.amtRelay?.trim();\n\tconst wantsAmt = input.mode === \"tunnel\" ||\n\t\t(input.mode !== \"native\" && relay !== undefined && relay !== \"\");\n\tif (!wantsAmt) {\n\t\tconst native: WindowMulticastSubscribeConfigBytes = {\n\t\t\t...base,\n\t\t\torigin: \"ip-multicast\",\n\t\t\t...(input.tracks?.length ? { tracks: input.tracks.map(({ name, packetId }) => ({ name, packetId })) } : {}),\n\t\t};\n\t\treturn native;\n\t}\n\n\tconst relayPort = input.amtRelayPort;\n\tif (!relay || relayPort === undefined || !isPositivePort(relayPort)) {\n\t\tthrow new TypeError(\"AMT requires a resolved relay and a valid relay port\");\n\t}\n\treturn {\n\t\t...base,\n\t\torigin: \"amt\",\n\t\trelay,\n\t\trelayPort,\n\t};\n}\n\nexport function isValidMulticastGroupAddress(address: string | undefined): boolean {\n\tconst value = address?.trim().toLowerCase();\n\tif (!value) return false;\n\tif (value.startsWith(\"ff\")) return isValidIpv6MulticastAddress(value);\n\n\tconst parts = value.split(\".\");\n\tif (parts.length !== 4) return false;\n\tconst octets = parts.map((part) => {\n\t\tif (!/^\\d+$/.test(part)) return Number.NaN;\n\t\tconst octet = Number(part);\n\t\treturn Number.isInteger(octet) && octet >= 0 && octet <= 255 ? octet : Number.NaN;\n\t});\n\treturn octets.every(Number.isInteger) && octets[0] >= 224 && octets[0] <= 239;\n}\n\nexport function normalizeMulticastEndpoint(\n\tendpoint: MulticastEndpointConfig,\n): MulticastEndpointConfig | undefined {\n\tconst groupAddress = endpoint.groupAddress.trim().toLowerCase();\n\tconst sourceAddress = endpoint.sourceAddress?.trim().toLowerCase() || undefined;\n\tconst amtRelay = endpoint.amtRelay?.trim().toLowerCase() || undefined;\n\tif (!isValidMulticastGroupAddress(groupAddress) || !isPositivePort(endpoint.port)) return undefined;\n\tif (endpoint.mode && ![\"auto\", \"native\", \"tunnel\"].includes(endpoint.mode)) return undefined;\n\tif (endpoint.amtRelayPort !== undefined && !isPositivePort(endpoint.amtRelayPort)) return undefined;\n\n\treturn {\n\t\tgroupAddress,\n\t\t...(sourceAddress ? { sourceAddress } : {}),\n\t\tport: endpoint.port,\n\t\t...(endpoint.mode ? { mode: endpoint.mode } : {}),\n\t\t...(amtRelay ? { amtRelay } : {}),\n\t\t...(endpoint.amtRelayPort ? { amtRelayPort: endpoint.amtRelayPort } : {}),\n\t};\n}\n\n/** Stable ownership key for one IWA multicast/AMT subscription. */\nexport function multicastEndpointKey(endpoint: MulticastEndpointConfig): string {\n\tconst normalized = normalizeMulticastEndpoint(endpoint);\n\tif (!normalized) throw new Error(\"Invalid multicast endpoint\");\n\treturn JSON.stringify([\n\t\tnormalized.sourceAddress ?? \"\",\n\t\tnormalized.groupAddress,\n\t\tnormalized.port,\n\t\tnormalized.mode ?? \"auto\",\n\t\tnormalized.amtRelay ?? \"\",\n\t\tnormalized.amtRelayPort ?? \"\",\n\t]);\n}\n\n/**\n * True when `value` may be advertised as a multicast track's MMTP `packet_id`:\n * an integer in 1..MMTP_PACKET_ID_MAX. packet_id 0 (MMTP_SIGNALING_PACKET_ID)\n * is the MMTP signaling flow -- the FFmpeg `moq_mmt` muxer sends the AL-FEC\n * message (0x0203) on it into the same group as the media -- so it is never a\n * track's. Only a number qualifies: a string, boolean or null is not coerced.\n */\nexport function isMulticastTrackPacketId(value: unknown): value is number {\n\treturn Number.isInteger(value) &&\n\t\t(value as number) > MMTP_SIGNALING_PACKET_ID &&\n\t\t(value as number) <= MMTP_PACKET_ID_MAX;\n}\n\nexport function isUsableMulticastEndpoint(\n\tendpoint: { groupAddress?: string; port?: unknown } | undefined,\n): boolean {\n\tconst port = Number(endpoint?.port);\n\treturn isValidMulticastGroupAddress(endpoint?.groupAddress) && isPositivePort(port);\n}\n\n/**\n * Unprefixed aliases of the `mc_` URL override keys, retired in BLO-37507, each\n * paired with the key that replaces it.\n */\nconst RETIRED_MULTICAST_URL_PARAMS = [\n\t[\"amt_relay\", \"mc_amt_relay\"],\n\t[\"amt_relay_port\", \"mc_amt_relay_port\"],\n] as const;\n\n/**\n * Reject a URL multicast override that still carries a retired unprefixed alias.\n *\n * Both URL readers call this only once a valid override is established (a valid\n * multicast `mc_group` and a valid `mc_port`; `readUrlMulticastConfig` also\n * requires the whole endpoint to normalize), which is where they used to read\n * the aliases: a link that\n * relied on one fails here with its replacement named, instead of silently\n * losing its AMT relay (and, in `readUrlMulticastConfig`, its tunnel mode).\n * Presence is the error, whatever the value and whether or not the `mc_` form\n * is also present: quietly preferring one of two keys is the tolerant read being\n * retired. Outside an override the readers read no override key at all, the\n * `mc_` forms included, so the bare names are left to the page; they are live\n * page parameters elsewhere, and keeping clear of those is what the `mc_`\n * namespace is for.\n */\nexport function assertNoRetiredMulticastUrlParams(params: URLSearchParams): void {\n\tfor (const [retired, replacement] of RETIRED_MULTICAST_URL_PARAMS) {\n\t\tif (params.has(retired)) {\n\t\t\tthrow new Error(\n\t\t\t\t`multicast URL override: query parameter \"${retired}\" is retired; use \"${replacement}\"`,\n\t\t\t);\n\t\t}\n\t}\n}\n\n/**\n * Read an explicit multicast endpoint override from `mc_*` URL parameters.\n * Throws when an override carries a retired unprefixed alias\n * (`assertNoRetiredMulticastUrlParams`).\n */\nexport function readUrlMulticastConfig(\n\tsearch: string | URLSearchParams = typeof location === \"undefined\" ? \"\" : location.search,\n): MulticastEndpointConfig | undefined {\n\tconst params = typeof search === \"string\" ? new URLSearchParams(search) : search;\n\tconst groupAddress = params.get(\"mc_group\") || \"\";\n\tconst port = positivePort(params.get(\"mc_port\"));\n\tif (!groupAddress || !port) return undefined;\n\n\tconst amtRelay = params.get(\"mc_amt_relay\") || undefined;\n\tconst amtRelayPort = positivePort(params.get(\"mc_amt_relay_port\"));\n\tconst requestedMode = params.get(\"mc_mode\");\n\tconst mode = amtRelay ? \"tunnel\" : multicastMode(requestedMode);\n\n\tconst endpoint = normalizeMulticastEndpoint({\n\t\tgroupAddress,\n\t\tsourceAddress: params.get(\"mc_source\") || undefined,\n\t\tport,\n\t\t...(mode ? { mode } : {}),\n\t\t...(amtRelay ? { amtRelay } : {}),\n\t\t...(amtRelayPort ? { amtRelayPort } : {}),\n\t});\n\t// An endpoint that does not normalize (e.g. a non-multicast mc_group) is no\n\t// override, so the page keeps its own bare names; the same scope\n\t// readPlayerMulticastConfig applies by validating the group first.\n\tif (endpoint) assertNoRetiredMulticastUrlParams(params);\n\treturn endpoint;\n}\n\nexport function isMulticastExplicitlyDisabled(\n\tsearch: string | URLSearchParams = typeof location === \"undefined\" ? \"\" : location.search,\n): boolean {\n\tconst params = typeof search === \"string\" ? new URLSearchParams(search) : search;\n\tconst multicast = params.get(\"multicast\")?.trim().toLowerCase();\n\tconst transport = params.get(\"transport\")?.trim().toLowerCase();\n\treturn transport === \"moq-only\" || [\"0\", \"false\", \"no\", \"off\"].includes(multicast || \"\");\n}\n\nfunction isValidIpv6MulticastAddress(value: string): boolean {\n\tif (!/^[0-9a-f:]+$/.test(value)) return false;\n\tconst compressed = value.includes(\"::\");\n\tif (value.indexOf(\"::\") !== value.lastIndexOf(\"::\")) return false;\n\tconst groups = value.split(\":\").filter(Boolean);\n\tif (groups.length === 0 || groups.length > 8) return false;\n\tif (!compressed && groups.length !== 8) return false;\n\treturn groups.every((group) => /^[0-9a-f]{1,4}$/.test(group));\n}\n\nfunction multicastMode(value: string | null): MulticastTransportPreference | undefined {\n\treturn value === \"native\" || value === \"tunnel\" || value === \"auto\" ? value : undefined;\n}\n\nfunction positivePort(value: string | null): number | undefined {\n\tconst port = Number(value);\n\treturn isPositivePort(port) ? port : undefined;\n}\n\nfunction isPositivePort(port: number): boolean {\n\treturn Number.isInteger(port) && port > 0 && port <= 65535;\n}\n", "import type { WindowMulticastSubscription } from \"./types.js\";\n\n/**\n * True when `subscription` delivers packets in batches through the `packets`\n * provider extension (BLO-36599).\n *\n * The pim window-multicast-bridge (0.2.0 and later) advertises the extension\n * in `eventTypes`. It builds only the events some listener asked for, so a\n * consumer that listens to `packets` alone gets one event per reader batch\n * instead of one DOM event per packet. A provider without the field, or one\n * that does not list `packets`, is a v1 provider: listen to `bytes`.\n */\nexport function subscriptionDeliversPacketBatches(\n\tsubscription: Pick<WindowMulticastSubscription, \"eventTypes\">,\n): boolean {\n\treturn subscription.eventTypes?.includes(\"packets\") === true;\n}\n", "import {\n\tmulticastEndpointKey,\n\tnormalizeMulticastEndpoint,\n\ttoWindowMulticastSubscribeConfig,\n} from \"./endpoint-config.js\";\nimport { subscriptionDeliversPacketBatches } from \"./packet-batch.js\";\nimport type {\n\tTransportConfig,\n\tWindowMulticastGateway,\n\tWindowMulticastSubscription,\n\tWindowMulticastSubscriptionState,\n} from \"./types.js\";\n\nexport interface MulticastSubscriptionConsumer {\n\tonBytes(data: ArrayBuffer): void;\n\tonState?(state: WindowMulticastSubscriptionState): void;\n\tonError?(error: Error): void;\n}\n\ninterface PoolEntry {\n\tsubscription: WindowMulticastSubscription;\n\tconsumers: Map<symbol, MulticastSubscriptionConsumer>;\n\t/**\n\t * Exactly one packet surface is listened to: `packets` when the provider\n\t * advertises it, `bytes` otherwise (BLO-36599).\n\t */\n\tpacketListener:\n\t\t| { type: \"bytes\"; listener: (event: CustomEvent<ArrayBuffer>) => void }\n\t\t| { type: \"packets\"; listener: (event: CustomEvent<readonly ArrayBuffer[]>) => void };\n\tstateListener: (event: CustomEvent<WindowMulticastSubscriptionState>) => void;\n\terrorListener: (event: CustomEvent<unknown>) => void;\n\tclosed: boolean;\n}\n\ninterface PoolRecord { claims: number; entry: Promise<PoolEntry>; }\n\nexport interface MulticastSubscriptionLease {\n\treadonly key: string;\n\trelease(): Promise<void>;\n}\n\n/** Shares only IWA multicast/AMT subscriptions. Unicast sessions remain per manager. */\nexport class MulticastSubscriptionPool {\n\treadonly #entries = new Map<string, PoolRecord>();\n\treadonly #gatewayIds = new WeakMap<object, number>();\n\t#nextGatewayId = 1;\n\n\tasync acquire(\n\t\tgateway: WindowMulticastGateway,\n\t\tconfig: TransportConfig,\n\t\tconsumer: MulticastSubscriptionConsumer,\n\t): Promise<MulticastSubscriptionLease> {\n\t\tconst endpoint = normalizeMulticastEndpoint({\n\t\t\tgroupAddress: config.group,\n\t\t\tsourceAddress: config.source,\n\t\t\tport: config.port,\n\t\t\tmode: config.mode,\n\t\t\tamtRelay: config.amtRelay,\n\t\t\tamtRelayPort: config.amtRelayPort,\n\t\t});\n\t\tif (!endpoint) throw new Error(\"Invalid multicast endpoint\");\n\t\tif (typeof gateway.subscribe !== \"function\") throw new Error(\"window.multicast.subscribe unavailable\");\n\n\t\tconst key = multicastEndpointKey(endpoint);\n\t\tconst internalKey = `${this.#gatewayId(gateway)}|${key}`;\n\t\tlet record = this.#entries.get(internalKey);\n\t\tif (!record) {\n\t\t\trecord = { claims: 0, entry: Promise.resolve(null as unknown as PoolEntry) };\n\t\t\tconst createdRecord = record;\n\t\t\trecord.entry = this.#createEntry(gateway, endpoint, (entry) => {\n\t\t\t\tif (this.#entries.get(internalKey) === createdRecord) this.#entries.delete(internalKey);\n\t\t\t\tthis.#detach(entry);\n\t\t\t}).catch((error) => {\n\t\t\t\tif (this.#entries.get(internalKey) === createdRecord) this.#entries.delete(internalKey);\n\t\t\t\tthrow error;\n\t\t\t});\n\t\t\tthis.#entries.set(internalKey, record);\n\t\t}\n\n\t\trecord.claims++;\n\t\tlet entry: PoolEntry;\n\t\ttry {\n\t\t\tentry = await record.entry;\n\t\t} catch (error) {\n\t\t\trecord.claims--;\n\t\t\tthrow error;\n\t\t}\n\t\tif (entry.closed) {\n\t\t\trecord.claims--;\n\t\t\treturn this.acquire(gateway, config, consumer);\n\t\t}\n\t\tconst token = Symbol(key);\n\t\tentry.consumers.set(token, consumer);\n\t\tlet released = false;\n\t\treturn {\n\t\t\tkey,\n\t\t\trelease: async () => {\n\t\t\t\tif (released) return;\n\t\t\t\treleased = true;\n\t\t\t\tentry.consumers.delete(token);\n\t\t\t\trecord.claims--;\n\t\t\t\tif (record.claims > 0) return;\n\t\t\t\tif (this.#entries.get(internalKey) === record) this.#entries.delete(internalKey);\n\t\t\t\tthis.#detach(entry);\n\t\t\t\tif (!entry.closed) await entry.subscription.unsubscribe();\n\t\t\t},\n\t\t};\n\t}\n\n\tget size(): number { return this.#entries.size; }\n\n\tasync #createEntry(\n\t\tgateway: WindowMulticastGateway,\n\t\tendpoint: NonNullable<ReturnType<typeof normalizeMulticastEndpoint>>,\n\t\tonClosed: (entry: PoolEntry) => void,\n\t): Promise<PoolEntry> {\n\t\tconst subscription = await gateway.subscribe!(toWindowMulticastSubscribeConfig({\n\t\t\tgroup: endpoint.groupAddress,\n\t\t\t...(endpoint.sourceAddress ? { source: endpoint.sourceAddress } : {}),\n\t\t\tport: endpoint.port,\n\t\t\t...(endpoint.mode ? { mode: endpoint.mode } : {}),\n\t\t\t...(endpoint.amtRelay ? { amtRelay: endpoint.amtRelay } : {}),\n\t\t\t...(endpoint.amtRelayPort ? { amtRelayPort: endpoint.amtRelayPort } : {}),\n\t\t}));\n\t\tconst consumers = new Map<symbol, MulticastSubscriptionConsumer>();\n\t\tlet entry: PoolEntry;\n\t\t// A batch is delivered as the same per-packet onBytes calls the bytes\n\t\t// surface makes, in arrival order and with each call isolated, so a\n\t\t// consumer cannot tell the two apart; only the DOM event per packet is\n\t\t// gone. The batch array and its elements are the provider's own, uncopied.\n\t\tconst packetListener: PoolEntry[\"packetListener\"] = subscriptionDeliversPacketBatches(subscription)\n\t\t\t? {\n\t\t\t\t\ttype: \"packets\",\n\t\t\t\t\tlistener: (event) => {\n\t\t\t\t\t\tfor (const data of event.detail) {\n\t\t\t\t\t\t\tthis.#dispatch(consumers, (consumer) => consumer.onBytes(data));\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t: {\n\t\t\t\t\ttype: \"bytes\",\n\t\t\t\t\tlistener: (event) => {\n\t\t\t\t\t\tthis.#dispatch(consumers, (consumer) => consumer.onBytes(event.detail));\n\t\t\t\t\t},\n\t\t\t\t};\n\t\tconst stateListener = (event: CustomEvent<WindowMulticastSubscriptionState>) => {\n\t\t\tthis.#dispatch(consumers, (consumer) => consumer.onState?.(event.detail));\n\t\t\tif (event.detail === \"closed\" && !entry.closed) {\n\t\t\t\tentry.closed = true;\n\t\t\t\tonClosed(entry);\n\t\t\t}\n\t\t};\n\t\tconst errorListener = (event: CustomEvent<unknown>) => {\n\t\t\tconst error = event.detail instanceof Error ? event.detail : new Error(String(event.detail));\n\t\t\tthis.#dispatch(consumers, (consumer) => consumer.onError?.(error));\n\t\t};\n\t\tentry = { subscription, consumers, packetListener, stateListener, errorListener, closed: false };\n\t\tif (packetListener.type === \"packets\") {\n\t\t\tsubscription.addEventListener(\"packets\", packetListener.listener);\n\t\t} else {\n\t\t\tsubscription.addEventListener(\"bytes\", packetListener.listener);\n\t\t}\n\t\tsubscription.addEventListener(\"state\", stateListener);\n\t\tsubscription.addEventListener(\"error\", errorListener);\n\t\treturn entry;\n\t}\n\n\t#gatewayId(gateway: WindowMulticastGateway): number {\n\t\tconst object = gateway as object;\n\t\tlet id = this.#gatewayIds.get(object);\n\t\tif (!id) {\n\t\t\tid = this.#nextGatewayId++;\n\t\t\tthis.#gatewayIds.set(object, id);\n\t\t}\n\t\treturn id;\n\t}\n\n\t#detach(entry: PoolEntry): void {\n\t\tconst { packetListener } = entry;\n\t\tif (packetListener.type === \"packets\") {\n\t\t\tentry.subscription.removeEventListener(\"packets\", packetListener.listener);\n\t\t} else {\n\t\t\tentry.subscription.removeEventListener(\"bytes\", packetListener.listener);\n\t\t}\n\t\tentry.subscription.removeEventListener(\"state\", entry.stateListener);\n\t\tentry.subscription.removeEventListener(\"error\", entry.errorListener);\n\t}\n\n\t#dispatch(\n\t\tconsumers: Map<symbol, MulticastSubscriptionConsumer>,\n\t\tcallback: (consumer: MulticastSubscriptionConsumer) => void,\n\t): void {\n\t\tfor (const consumer of consumers.values()) {\n\t\t\ttry { callback(consumer); } catch (error) { console.error(\"[Transport] multicast consumer callback failed\", error); }\n\t\t}\n\t}\n}\n\nexport const multicastSubscriptionPool = new MulticastSubscriptionPool();\n", "/**\n * @blockcast/transport - Transport Manager\n *\n * Unified transport abstraction for MoQ players supporting:\n * - window.multicast gateway subscriptions\n * - AMT proxy tunneling (RFC 7450) with optional DRIAD relay discovery\n * - WebTransport/QUIC (MoQ-lite protocol)\n * - WebSocket fallback\n *\n * Transport promotion hierarchy (WebTransport-first for fast startup):\n * 1. WebTransport - immediate connection for fast channel select\n * 2. Background discovery - check if window.multicast is available\n * 3. Promote to gateway multicast if available\n * 4. WebSocket fallback if WebTransport unavailable\n *\n * Features:\n * - Progressive adaptive bitrate based on network conditions\n * - FEC (Forward Error Correction) for burst loss recovery\n * - Seamless transport switching with session continuity\n * - Unified FEC/ABR pipeline across all transport modes\n */\n\nimport type {\n\tTransportConfig,\n\tTransportConnectConfig,\n\tTransportSubscription,\n\tTransportCapabilities,\n\tTransportState,\n\tQualityLevel,\n\tWindowMulticastCapabilities,\n\tWindowMulticastGateway,\n\tWindowMulticastSubscriptionState,\n} from \"./types.js\";\nimport { DRIADDiscovery } from \"./driad-discovery.js\";\nimport { AdaptiveBitrateController } from \"./abr-controller.js\";\nimport {\n\tMulticastOrchestrator,\n\ttype MulticastTierPacket,\n\ttype OrchestratorCommand,\n\ttype OrchestratorOptions,\n} from \"./multicast-orchestrator.js\";\nimport type { MulticastTierEndpoint } from \"./multicast-tier.js\";\nimport { multicastSubscriptionPool } from \"./multicast-subscription-pool.js\";\nimport { normalizeMulticastEndpoint } from \"./endpoint-config.js\";\n\n/**\n * Transport Manager\n *\n * Manages transport connections with automatic mode selection:\n * WebTransport -> window.multicast -> AMT proxy -> WebSocket\n */\nexport class TransportManager {\n\tprivate state: TransportState = {\n\t\tmode: \"disconnected\",\n\t\tconnected: false,\n\t\tpacketsReceived: 0,\n\t\tbytesReceived: 0,\n\t\tlastPacketTime: 0,\n\t};\n\n\tprivate capabilities: TransportCapabilities | null = null;\n\tprivate subscription: TransportSubscription | null = null;\n\tprivate driad = new DRIADDiscovery();\n\t/** Built by `connect()` from the caller's ladder; there is no built-in one. */\n\tprivate abr: AdaptiveBitrateController | null = null;\n\tprivate multicastOrchestrator: MulticastOrchestrator | null = null;\n\tprivate onOrchestratorCommand?: (command: OrchestratorCommand) => void;\n\tprivate connectionGeneration = 0;\n\n\tprivate onPacket?: (data: ArrayBuffer) => void;\n\tprivate onError?: (error: Error) => void;\n\tprivate onStateChange?: (state: TransportState) => void;\n\n\tprivate getWindow(): Window | undefined {\n\t\treturn typeof window === \"undefined\" ? undefined : window;\n\t}\n\n\tprivate getWindowMulticastGateway(): WindowMulticastGateway | undefined {\n\t\treturn this.getWindow()?.multicast;\n\t}\n\n\tprivate async getGatewayCapabilities(\n\t\tgateway: WindowMulticastGateway,\n\t): Promise<WindowMulticastCapabilities | undefined> {\n\t\tif (!gateway?.capabilities) return undefined;\n\t\ttry {\n\t\t\treturn typeof gateway.capabilities === \"function\" ?\n\t\t\t\tawait gateway.capabilities() :\n\t\t\t\tgateway.capabilities;\n\t\t} catch (error) {\n\t\t\tconsole.warn(\"[Transport] window.multicast capabilities failed:\", error);\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tprivate async gatewaySupportsBytes(gateway: WindowMulticastGateway | undefined): Promise<boolean> {\n\t\tif (typeof gateway?.subscribe !== \"function\") return false;\n\t\tif (!gateway.capabilities) return true;\n\n\t\tconst capabilities = await this.getGatewayCapabilities(gateway);\n\t\tif (!capabilities) return false;\n\t\tconst consumeModes = capabilities.consumeModes;\n\t\treturn !consumeModes || consumeModes.includes(\"bytes\");\n\t}\n\n\t/**\n\t * Detect available transport capabilities\n\t */\n\tasync detectCapabilities(): Promise<TransportCapabilities> {\n\t\tif (this.capabilities) return this.capabilities;\n\n\t\tconst gateway = this.getWindowMulticastGateway();\n\t\tconst hasGatewayBytes = await this.gatewaySupportsBytes(gateway);\n\t\tconst win = this.getWindow();\n\n\t\tthis.capabilities = {\n\t\t\tnativeSSM: hasGatewayBytes,\n\t\t\tnativeIGMP: hasGatewayBytes,\n\t\t\tdirectSockets: typeof win?.UDPSocket !== \"undefined\",\n\t\t\tamt: false,\n\t\t\tdriad: true, // DRIAD uses HTTP proxy, always available\n\t\t\tfecDecode: false,\n\t\t};\n\n\t\tconsole.log(\"[Transport] Capabilities:\", this.capabilities);\n\t\treturn this.capabilities;\n\t}\n\n\t/**\n\t * Connect using best available transport\n\t *\n\t * Promotion hierarchy:\n\t * 1. window.multicast gateway bytes\n\t * 2. AMT proxy (specified or discovered via DRIAD)\n\t * 3. MOQ WebTransport (final fallback)\n\t *\n\t * The ABR ladder (`abrLadder`) and initial rendition (`initialQuality`)\n\t * are checked first, before anything is torn down. A config that fails is\n\t * refused with the same shape as an invalid endpoint, the error to the\n\t * error callback and `false` resolved; unlike one, it is caught before the\n\t * live subscription closes, so a refused reconnect leaves that subscription,\n\t * its state and any connect already in flight untouched.\n\t */\n\tasync connect(config: TransportConnectConfig): Promise<boolean> {\n\t\tlet abr: AdaptiveBitrateController;\n\t\ttry {\n\t\t\tabr = new AdaptiveBitrateController({\n\t\t\t\tladder: config.abrLadder,\n\t\t\t\tinitialQuality: config.initialQuality?.name,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\t// A refused config is a caller bug. Without an error callback, say so\n\t\t\t// on the console, since `false` alone also means \"no multicast path\";\n\t\t\t// with one, the callback is the report.\n\t\t\tconst refused = error instanceof Error ? error : new Error(String(error));\n\t\t\tif (this.onError) this.onError(refused);\n\t\t\telse console.error(\"[Transport] ABR config refused, not connecting:\", refused);\n\t\t\treturn false;\n\t\t}\n\t\tconst generation = ++this.connectionGeneration;\n\t\tthis.subscription?.close();\n\t\tthis.subscription = null;\n\t\t// The previous connection's ladder ends with it, as in disconnect(): a\n\t\t// reconnect that bails below (invalid endpoint, or superseded while\n\t\t// detecting capabilities) leaves no ladder, and only the success path\n\t\t// installs `abr`.\n\t\tthis.abr = null;\n\t\tthis.state = {\n\t\t\tmode: \"disconnected\",\n\t\t\tconnected: false,\n\t\t\tpacketsReceived: 0,\n\t\t\tbytesReceived: 0,\n\t\t\tlastPacketTime: 0,\n\t\t};\n\t\tthis.onStateChange?.(this.state);\n\t\tconst endpoint = normalizeMulticastEndpoint({\n\t\t\tgroupAddress: config.group,\n\t\t\tsourceAddress: config.source,\n\t\t\tport: config.port,\n\t\t\tmode: config.mode,\n\t\t\tamtRelay: config.amtRelay,\n\t\t\tamtRelayPort: config.amtRelayPort,\n\t\t});\n\t\tif (!endpoint) {\n\t\t\tconst error = new Error(\"Invalid multicast endpoint configuration\");\n\t\t\tthis.onError?.(error);\n\t\t\treturn false;\n\t\t}\n\t\tconfig = {\n\t\t\t...config,\n\t\t\tgroup: endpoint.groupAddress,\n\t\t\tsource: endpoint.sourceAddress ?? \"\",\n\t\t\tmode: endpoint.mode,\n\t\t\tamtRelay: endpoint.amtRelay,\n\t\t\tamtRelayPort: endpoint.amtRelayPort,\n\t\t};\n\t\tconst caps = await this.detectCapabilities();\n\t\tif (generation !== this.connectionGeneration) return false;\n\n\t\t// ABR adapts over the caller's ladder (the catalog's renditions),\n\t\t// validated above before anything was torn down.\n\t\tthis.abr = abr;\n\n\t\tabr.setCallback((quality) => {\n\t\t\tthis.state.quality = quality;\n\t\t\tthis.subscription?.onqualitychange?.(quality);\n\t\t\tthis.onStateChange?.(this.state);\n\t\t});\n\n\t\t// Try transport hierarchy\n\t\tlet connected = false;\n\n\t\t// 1. Try the canonical browser multicast gateway.\n\t\tif (caps.nativeSSM || caps.nativeIGMP) {\n\t\t\tconsole.log(\"[Transport] Attempting window.multicast...\");\n\t\t\tconnected = await this.connectWindowMulticast(config, generation);\n\t\t\tif (connected) {\n\t\t\t\tif (generation !== this.connectionGeneration) return false;\n\t\t\t\tthis.state.mode = \"multicast\";\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\t// 2. Try AMT proxy (with optional DRIAD discovery)\n\t\tif (!connected) {\n\t\t\tconsole.log(\"[Transport] Attempting AMT tunnel...\");\n\t\t\tconnected = await this.connectAmt(config, generation);\n\t\t\tif (connected) {\n\t\t\t\tif (generation !== this.connectionGeneration) return false;\n\t\t\t\tthis.state.mode = \"amt\";\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\t// 3. Fallback to MOQ\n\t\tconsole.log(\"[Transport] All multicast transports failed, falling back to MOQ\");\n\t\treturn false;\n\t}\n\n\t/**\n\t * Connect via the lowercase window.multicast bytes gateway.\n\t */\n\tprivate async connectWindowMulticast(config: TransportConfig, generation: number): Promise<boolean> {\n\t\ttry {\n\t\t\tconst gateway = this.getWindowMulticastGateway();\n\t\t\tif (typeof gateway?.subscribe !== \"function\") return false;\n\n\t\t\tconst lease = await multicastSubscriptionPool.acquire(gateway, config, {\n\t\t\t\tonBytes: (data) => {\n\t\t\t\t\tif (generation === this.connectionGeneration) this.handlePacket(data);\n\t\t\t\t},\n\t\t\t\tonState: (state: WindowMulticastSubscriptionState) => {\n\t\t\t\t\tif (generation !== this.connectionGeneration) return;\n\t\t\t\t\tthis.state.connected = state !== \"closed\";\n\t\t\t\t\tthis.onStateChange?.(this.state);\n\t\t\t\t},\n\t\t\t\tonError: (error) => {\n\t\t\t\t\tif (generation !== this.connectionGeneration) return;\n\t\t\t\t\tconsole.error(\"[Transport] window.multicast error:\", error);\n\t\t\t\t\tthis.onError?.(error);\n\t\t\t\t},\n\t\t\t});\n\t\t\tif (generation !== this.connectionGeneration) {\n\t\t\t\tawait lease.release();\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tthis.subscription = {\n\t\t\t\tid: lease.key,\n\t\t\t\tconfig,\n\t\t\t\tclose: () => { void lease.release(); },\n\t\t\t};\n\n\t\t\tthis.state = {\n\t\t\t\tmode: \"multicast\",\n\t\t\t\tconnected: true,\n\t\t\t\tgroup: config.group,\n\t\t\t\tsource: config.source,\n\t\t\t\tquality: this.requireAbr().getCurrentQuality(),\n\t\t\t\tpacketsReceived: 0,\n\t\t\t\tbytesReceived: 0,\n\t\t\t\tlastPacketTime: 0,\n\t\t\t};\n\n\t\t\tconsole.log(`[Transport] Connected via window.multicast: (${config.source}, ${config.group})`);\n\t\t\tthis.onStateChange?.(this.state);\n\t\t\treturn true;\n\t\t} catch (error) {\n\t\t\tconsole.warn(\"[Transport] window.multicast failed:\", error);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Connect via AMT proxy (with optional DRIAD discovery)\n\t */\n\tprivate async connectAmt(config: TransportConfig, generation: number): Promise<boolean> {\n\t\ttry {\n\t\t\tlet amtRelay = config.amtRelay;\n\n\t\t\t// Use DRIAD to discover relay if not specified (RFC 8777)\n\t\t\t// DRIAD discovers relays based on SOURCE address, not group\n\t\t\tif (!amtRelay && config.driadEnabled !== false && config.source) {\n\t\t\t\tconsole.log(`[Transport] Discovering AMT relay via DRIAD for source ${config.source}...`);\n\t\t\t\tconst relays = await this.driad.discoverRelays(config.source);\n\t\t\t\tif (generation !== this.connectionGeneration) return false;\n\t\t\t\tconst bestRelay = await this.driad.selectBestRelay(relays);\n\t\t\t\tif (generation !== this.connectionGeneration) return false;\n\n\t\t\t\tif (bestRelay) {\n\t\t\t\t\tamtRelay = bestRelay.host;\n\t\t\t\t\tthis.state.amtRelay = amtRelay;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!amtRelay) {\n\t\t\t\tconsole.warn(\"[Transport] No AMT relay available (DRIAD requires source address for SSM)\");\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// Use the WebSocket-based AMT proxy when a relay is known.\n\t\t\treturn await this.connectAmtProxy(config, amtRelay, generation);\n\t\t} catch (error) {\n\t\t\tconsole.warn(\"[Transport] AMT connection failed:\", error);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Connect via WebSocket-based AMT proxy (browser fallback)\n\t */\n\tprivate async connectAmtProxy(config: TransportConfig, amtRelay: string, generation: number): Promise<boolean> {\n\t\ttry {\n\t\t\tconst relayHost = amtRelay.includes(\":\") && !amtRelay.startsWith(\"[\") ? `[${amtRelay}]` : amtRelay;\n\t\t\t// amtRelayPort is the relay's RFC 7450 UDP port, not the proxy's HTTPS port.\n\t\t\tconst wsUrl = new URL(`wss://${relayHost}/amt-proxy`);\n\t\t\twsUrl.searchParams.set(\"group\", config.group);\n\t\t\twsUrl.searchParams.set(\"source\", config.source || \"\");\n\t\t\twsUrl.searchParams.set(\"port\", String(config.port));\n\n\t\t\tconst ws = new WebSocket(wsUrl);\n\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tws.binaryType = \"arraybuffer\";\n\t\t\t\tconst timeout = setTimeout(() => {\n\t\t\t\t\tws.close();\n\t\t\t\t\treject(new Error(\"AMT proxy connection timeout\"));\n\t\t\t\t}, 10000);\n\n\t\t\t\tws.onopen = () => {\n\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t\tif (generation !== this.connectionGeneration) {\n\t\t\t\t\t\tws.close();\n\t\t\t\t\t\tresolve(false);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconsole.log(`[Transport] Connected to AMT proxy: ${amtRelay}`);\n\n\t\t\t\t\tthis.subscription = {\n\t\t\t\t\t\tid: `amt-proxy-${Date.now()}`,\n\t\t\t\t\t\tconfig,\n\t\t\t\t\t\tclose: () => ws.close(),\n\t\t\t\t\t};\n\n\t\t\t\t\tthis.state = {\n\t\t\t\t\t\tmode: \"amt\",\n\t\t\t\t\t\tconnected: true,\n\t\t\t\t\t\tgroup: config.group,\n\t\t\t\t\t\tsource: config.source,\n\t\t\t\t\t\tamtRelay,\n\t\t\t\t\t\tquality: this.requireAbr().getCurrentQuality(),\n\t\t\t\t\t\tpacketsReceived: 0,\n\t\t\t\t\t\tbytesReceived: 0,\n\t\t\t\t\t\tlastPacketTime: 0,\n\t\t\t\t\t};\n\n\t\t\t\t\tthis.onStateChange?.(this.state);\n\t\t\t\t\tresolve(true);\n\t\t\t\t};\n\n\t\t\t\tws.onmessage = (event) => {\n\t\t\t\t\tif (generation === this.connectionGeneration && event.data instanceof ArrayBuffer) {\n\t\t\t\t\t\tthis.handlePacket(event.data);\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tws.onerror = (error) => {\n\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t\tif (generation !== this.connectionGeneration) {\n\t\t\t\t\t\tresolve(false);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconsole.error(\"[Transport] AMT proxy error:\", error);\n\t\t\t\t\treject(error);\n\t\t\t\t};\n\n\t\t\t\tws.onclose = () => {\n\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t\tif (generation !== this.connectionGeneration) {\n\t\t\t\t\t\tresolve(false);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tthis.state.connected = false;\n\t\t\t\t\tthis.onStateChange?.(this.state);\n\t\t\t\t\tresolve(false);\n\t\t\t\t};\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tconsole.warn(\"[Transport] AMT proxy failed:\", error);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Handle received packet\n\t */\n\tprivate handlePacket(data: ArrayBuffer) {\n\t\tthis.state.packetsReceived++;\n\t\tthis.state.bytesReceived += data.byteLength;\n\t\tthis.state.lastPacketTime = Date.now();\n\n\t\t// Update ABR with sample\n\t\t// Packets reach here only through a connect()ed join, which built the ABR.\n\t\tthis.abr?.recordSample(data.byteLength, 0);\n\n\t\t// Forward to callback\n\t\tthis.onPacket?.(data);\n\t}\n\n\t/**\n\t * Get current transport state\n\t */\n\tgetState(): TransportState {\n\t\treturn { ...this.state };\n\t}\n\n\t/**\n\t * The rung of the live connection's `abrLadder` ABR is on; `null` when\n\t * there is no ladder (before `connect(config)` succeeds, after\n\t * `disconnect()`, or after a reconnect that was torn down and bailed).\n\t */\n\tgetQuality(): QualityLevel | null {\n\t\treturn this.abr?.getCurrentQuality() ?? null;\n\t}\n\n\t/**\n\t * Force the rung named `name` of the live connection's `abrLadder`.\n\t * Throws `Error` when there is no ladder (see `getQuality()`), and\n\t * `RangeError` when the ladder has no rung of that name; it never falls\n\t * back to another rung.\n\t */\n\tsetQuality(name: string) {\n\t\tthis.requireAbr().setQuality(name);\n\t}\n\n\t/** The controller `connect()` built; before it there is no ladder to adapt over. */\n\tprivate requireAbr(): AdaptiveBitrateController {\n\t\tif (!this.abr) throw new Error(\"[Transport] ABR has no ladder before connect(config)\");\n\t\treturn this.abr;\n\t}\n\n\t/** Configures multicast-only ABR while leaving the caller's MoQ ABR untouched. */\n\tconfigureMulticastTiers(\n\t\toptions: OrchestratorOptions,\n\t\tonCommand?: (command: OrchestratorCommand) => void,\n\t): MulticastTierEndpoint | undefined {\n\t\tthis.dispatchOrchestratorCommand(this.multicastOrchestrator?.cancel());\n\t\tthis.multicastOrchestrator = new MulticastOrchestrator(options);\n\t\tthis.onOrchestratorCommand = onCommand;\n\t\treturn this.multicastOrchestrator.start();\n\t}\n\n\trecordMulticastGoodput(bitrate: number | undefined): OrchestratorCommand | undefined {\n\t\treturn this.dispatchOrchestratorCommand(this.multicastOrchestrator?.onGoodput({ bitrate }));\n\t}\n\n\trecordMulticastHeadroom(healthy: boolean): OrchestratorCommand | undefined {\n\t\treturn this.dispatchOrchestratorCommand(this.multicastOrchestrator?.onHeadroom({ healthy }));\n\t}\n\n\trecordMulticastTierPacket(packet: MulticastTierPacket): OrchestratorCommand | undefined {\n\t\treturn this.dispatchOrchestratorCommand(this.multicastOrchestrator?.onTierPacket(packet));\n\t}\n\n\tpollMulticastTierSwitch(): OrchestratorCommand | undefined {\n\t\treturn this.dispatchOrchestratorCommand(this.multicastOrchestrator?.poll());\n\t}\n\n\t/**\n\t * Set packet callback\n\t */\n\tsetPacketCallback(callback: (data: ArrayBuffer) => void) {\n\t\tthis.onPacket = callback;\n\t}\n\n\t/**\n\t * Set error callback\n\t */\n\tsetErrorCallback(callback: (error: Error) => void) {\n\t\tthis.onError = callback;\n\t}\n\n\t/**\n\t * Set state change callback\n\t */\n\tsetStateCallback(callback: (state: TransportState) => void) {\n\t\tthis.onStateChange = callback;\n\t}\n\n\t/**\n\t * Disconnect\n\t */\n\tdisconnect() {\n\t\tthis.connectionGeneration++;\n\t\tthis.subscription?.close();\n\t\tthis.subscription = null;\n\t\t// No ladder after a disconnect, as before the first connect():\n\t\t// getQuality() is null and setQuality() throws until connect(config).\n\t\tthis.abr = null;\n\t\tthis.dispatchOrchestratorCommand(this.multicastOrchestrator?.cancel());\n\t\tthis.multicastOrchestrator = null;\n\t\tthis.onOrchestratorCommand = undefined;\n\t\tthis.state = {\n\t\t\tmode: \"disconnected\",\n\t\t\tconnected: false,\n\t\t\tpacketsReceived: 0,\n\t\t\tbytesReceived: 0,\n\t\t\tlastPacketTime: 0,\n\t\t};\n\t\tthis.onStateChange?.(this.state);\n\t}\n\n\tprivate dispatchOrchestratorCommand(command: OrchestratorCommand | undefined): OrchestratorCommand | undefined {\n\t\tif (command) this.onOrchestratorCommand?.(command);\n\t\treturn command;\n\t}\n}\n\n// Export singleton instance\nexport const transportManager = new TransportManager();\n", "const MAX_MOQT_PRIORITY = 0xff;\n\ndeclare const subscriberPriorityBrand: unique symbol;\ndeclare const publisherPriorityBrand: unique symbol;\n\nexport type MoqtSubscriberPriority = number & { readonly [subscriberPriorityBrand]: true };\nexport type MoqtPublisherPriority = number & { readonly [publisherPriorityBrand]: true };\n\nfunction moqtPriority(value: number, field: string): number {\n\tif (!Number.isInteger(value) || value < 0 || value > MAX_MOQT_PRIORITY) {\n\t\tthrow new RangeError(`${field} must be an integer in [0, ${MAX_MOQT_PRIORITY}], got ${value}`);\n\t}\n\treturn value;\n}\n\n/** Validate a protocol-native MOQT subscriber priority without transforming it. */\nexport function moqtSubscriberPriority(value: number): MoqtSubscriberPriority {\n\treturn moqtPriority(value, \"MOQT subscriber priority\") as MoqtSubscriberPriority;\n}\n\n/** Validate a protocol-native MOQT publisher priority without transforming it. */\nexport function moqtPublisherPriority(value: number): MoqtPublisherPriority {\n\treturn moqtPriority(value, \"MOQT publisher priority\") as MoqtPublisherPriority;\n}\n\n/** Shared media subscription policy on MOQT's native lower-number-wins scale. */\nexport const MOQT_SUBSCRIBER_PRIORITY = Object.freeze({\n\tcatalog: moqtSubscriberPriority(0),\n\t/**\n\t * @deprecated A repair FETCH's SUBSCRIBER_PRIORITY is derived per request\n\t * from the catalog source and repair priorities (draft-ramadan-moq-fec\n\t * \u00A711.4.5), not a player constant. Use the `subscriberPriority` that\n\t * `@blockcast/mmt-container`'s `wireRepairFetch` puts on each\n\t * `RepairFetchRequest` (`repairFetchSubscriberPriority`).\n\t */\n\tdeadlineRepair: moqtSubscriberPriority(0),\n\taudio: moqtSubscriberPriority(1),\n\tvideo: moqtSubscriberPriority(2),\n\t/**\n\t * @deprecated See `deadlineRepair`: use the request's catalog-derived\n\t * `RepairFetchRequest.subscriberPriority` (draft-ramadan-moq-fec \u00A711.4.5).\n\t */\n\tbackgroundRepair: moqtSubscriberPriority(3),\n});\n\n/** Publisher priority bands from draft-ramadan-moq-fec section 10. */\nexport const MOQT_PUBLISHER_PRIORITY = Object.freeze({\n\tcatalog: moqtPublisherPriority(32),\n\tsource: moqtPublisherPriority(128),\n\trepair: moqtPublisherPriority(240),\n});\n", "/**\n * @blockcast/transport - AMT Gateway\n *\n * Consolidated AMT (Automatic Multicast Tunneling) implementation\n * supporting both Service Worker and Direct Sockets approaches.\n *\n * RFC 7450: Automatic Multicast Tunneling\n */\n\nimport type { GroupKey, PacketMetadata, AmtRelayInfo } from \"./types.js\";\nimport { DRIADDiscovery } from \"./driad-discovery.js\";\nimport { DEFAULT_AMT_RELAY_PORT } from \"@blockcast/mmt-base\";\n\n/** AMT message types */\nconst AMT_MESSAGE_TYPES = {\n\tRELAY_DISCOVERY: 1,\n\tRELAY_ADVERTISEMENT: 2,\n\tREQUEST: 3,\n\tMEMBERSHIP_QUERY: 4,\n\tMEMBERSHIP_UPDATE: 5,\n\tMULTICAST_DATA: 6,\n\tTEARDOWN: 7,\n} as const;\n\n/** AMT gateway states */\nconst GATEWAY_STATES = {\n\tIDLE: \"idle\",\n\tDISCOVERING: \"discovering\",\n\tREQUESTING: \"requesting\",\n\tQUERYING: \"querying\",\n\tACTIVE: \"active\",\n\tLEAVING: \"leaving\",\n\tCLOSED: \"closed\",\n} as const;\n\ntype GatewayState = (typeof GATEWAY_STATES)[keyof typeof GATEWAY_STATES];\n\n/** AMT subscription configuration */\nexport interface AMTSubscriptionConfig {\n\tkey: GroupKey;\n\trelayAddress?: string;\n\trelayPort?: number;\n\tonPacket: (data: ArrayBuffer, metadata: PacketMetadata) => void;\n\tonError: (error: Error) => void;\n}\n\n/** AMT packet info */\nexport interface AMTPacketInfo {\n\tdata: Uint8Array;\n\tsrcAddress: string;\n\tdstAddress: string;\n\tsrcPort: number;\n\tdstPort: number;\n\tgroupKey: string;\n\ttimestamp: number;\n}\n\n/** AMT Gateway options */\nexport interface AMTGatewayOptions {\n\trelayAddress: string;\n\trelayPort?: number;\n\tonPacket?: (packet: AMTPacketInfo) => void;\n\tonStateChange?: (newState: GatewayState, oldState: GatewayState) => void;\n\tonError?: (error: Error) => void;\n\tonLog?: (message: string) => void;\n}\n\n/** Active AMT subscription */\ninterface AMTSubscription {\n\tkey: GroupKey;\n\tsubscriptionId: string;\n\tconfig: AMTSubscriptionConfig;\n\trelay?: AmtRelayInfo;\n\thandshakeComplete: boolean;\n\tkeepAliveTimer?: ReturnType<typeof setInterval>;\n\tlastKeepAlive?: number;\n}\n\n/** Check if address is IPv4 */\nfunction isIPv4(address: string): boolean {\n\treturn /^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/.test(address);\n}\n\n/**\n * AMT Gateway Manager (Service Worker based)\n *\n * Manages AMT tunnel subscriptions using Service Worker for UDP communication.\n * Handles DRIAD discovery, AMT handshake, and packet decapsulation.\n */\nexport class AMTGatewayManager {\n\tprivate subscriptions = new Map<string, AMTSubscription>();\n\tprivate messageHandler?: (event: MessageEvent) => void;\n\tprivate pendingPackets = new Map<string, Array<{ data: ArrayBuffer; remoteAddress: string; remotePort: number }>>();\n\tprivate driad = new DRIADDiscovery();\n\n\tconstructor() {\n\t\t// Set up message handler for packets from service worker\n\t\tthis.messageHandler = (event: MessageEvent) => {\n\t\t\tconst data = event.data as { type: string; subscriptionId?: string; data?: unknown; error?: string; message?: string };\n\t\t\tconsole.log(`[AMT] SW message received:`, data.type);\n\t\t\tif (data.type === \"UDP_PACKET\") {\n\t\t\t\tthis.handlePacket(data);\n\t\t\t} else if (data.type === \"SOCKET_ERROR\") {\n\t\t\t\tthis.handleSocketError(data);\n\t\t\t} else if (data.type === \"DEBUG_LOG\") {\n\t\t\t\tconsole.log(`[AMT SW Debug] ${data.message}`);\n\t\t\t}\n\t\t};\n\n\t\t// Register message handler\n\t\tif (typeof navigator !== \"undefined\" && navigator.serviceWorker) {\n\t\t\tnavigator.serviceWorker.addEventListener(\"message\", this.messageHandler);\n\t\t\tconsole.log(\"[AMT] Registered service worker message handler\");\n\t\t} else {\n\t\t\tconsole.warn(\"[AMT] Service worker not available\");\n\t\t}\n\t}\n\n\t/**\n\t * Subscribe via AMT tunnel\n\t */\n\tasync subscribe(config: AMTSubscriptionConfig): Promise<string> {\n\t\tconst { key } = config;\n\t\tconst keyStr = this.getKeyString(key);\n\n\t\tif (this.subscriptions.has(keyStr)) {\n\t\t\tconsole.log(`[AMT] Reusing existing subscription to ${keyStr}`);\n\t\t\treturn keyStr;\n\t\t}\n\n\t\ttry {\n\t\t\tlet relayAddress: string;\n\t\t\tlet discoveryMethod: \"driad\" | \"manual\";\n\t\t\tconst relayPort = config.relayPort || DEFAULT_AMT_RELAY_PORT;\n\n\t\t\tif (config.relayAddress) {\n\t\t\t\trelayAddress = config.relayAddress;\n\t\t\t\tdiscoveryMethod = \"manual\";\n\t\t\t\tconsole.log(`[AMT] Using configured relay ${relayAddress}:${relayPort}`);\n\t\t\t} else if (key.source) {\n\t\t\t\t// DRIAD (RFC 8777) discovers relays based on SOURCE address, not group\n\t\t\t\tconsole.log(`[AMT] No relay configured, discovering via DRIAD for source ${key.source}...`);\n\t\t\t\tconst relays = await this.driad.discoverRelays(key.source);\n\t\t\t\tconst bestRelay = await this.driad.selectBestRelay(relays);\n\n\t\t\t\tif (!bestRelay) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`AMT relay discovery failed for source ${key.source}. ` +\n\t\t\t\t\t\t\t`Please specify a relay address in the subscription configuration.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\trelayAddress = bestRelay.host;\n\t\t\t\tdiscoveryMethod = \"driad\";\n\t\t\t\tconsole.log(`[AMT] Discovered relay via DRIAD: ${relayAddress}:${relayPort}`);\n\t\t\t} else {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`AMT relay discovery requires a source address for SSM (RFC 8777). ` +\n\t\t\t\t\t\t`Please specify either a relay address or source address.`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst subscriptionId = keyStr;\n\n\t\t\t// Initialize packet queue for handshake\n\t\t\tthis.pendingPackets.set(subscriptionId, []);\n\n\t\t\t// Create subscription\n\t\t\tconst subscription: AMTSubscription = {\n\t\t\t\tkey,\n\t\t\t\tsubscriptionId,\n\t\t\t\tconfig,\n\t\t\t\thandshakeComplete: false,\n\t\t\t\trelay: {\n\t\t\t\t\taddress: relayAddress,\n\t\t\t\t\tport: relayPort,\n\t\t\t\t\tdiscoveryMethod,\n\t\t\t\t\tdiscoveredAt: Date.now(),\n\t\t\t\t},\n\t\t\t};\n\n\t\t\tthis.subscriptions.set(keyStr, subscription);\n\n\t\t\t// Note: Actual UDP socket creation and handshake would require\n\t\t\t// Service Worker communication - this is a simplified version\n\t\t\tconsole.log(`[AMT] Subscription created for ${keyStr} via ${relayAddress}:${relayPort}`);\n\n\t\t\treturn keyStr;\n\t\t} catch (error) {\n\t\t\tconst subscription = this.subscriptions.get(keyStr);\n\t\t\tif (subscription) {\n\t\t\t\tthis.subscriptions.delete(keyStr);\n\t\t\t\tthis.pendingPackets.delete(subscription.subscriptionId);\n\t\t\t}\n\n\t\t\tthrow new Error(`Failed to subscribe via AMT: ${error instanceof Error ? error.message : String(error)}`);\n\t\t}\n\t}\n\n\t/**\n\t * Unsubscribe from AMT tunnel\n\t */\n\tasync unsubscribe(handle: string): Promise<void> {\n\t\tconst subscription = this.subscriptions.get(handle);\n\t\tif (!subscription) {\n\t\t\tthrow new Error(`Subscription ${handle} not found`);\n\t\t}\n\n\t\ttry {\n\t\t\t// Stop keep-alive timer\n\t\t\tif (subscription.keepAliveTimer) {\n\t\t\t\tclearInterval(subscription.keepAliveTimer);\n\t\t\t}\n\n\t\t\tconsole.log(`[AMT] Unsubscribed from ${handle}`);\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[AMT] Error during unsubscribe:\", error);\n\t\t} finally {\n\t\t\tthis.subscriptions.delete(handle);\n\t\t\tthis.pendingPackets.delete(subscription.subscriptionId);\n\t\t}\n\t}\n\n\t/**\n\t * Get list of active subscriptions\n\t */\n\tgetSubscriptions(): string[] {\n\t\treturn Array.from(this.subscriptions.keys());\n\t}\n\n\t/**\n\t * Handle packet from service worker\n\t */\n\tprivate handlePacket(message: { subscriptionId?: string; data?: unknown }): void {\n\t\tconst { subscriptionId, data } = message;\n\t\tif (!subscriptionId) return;\n\n\t\tconst subscription = this.subscriptions.get(subscriptionId);\n\t\tif (!subscription) {\n\t\t\tconsole.warn(`[AMT] Received packet for unknown subscription: ${subscriptionId}`);\n\t\t\treturn;\n\t\t}\n\n\t\t// During handshake, queue packets\n\t\tif (!subscription.handshakeComplete) {\n\t\t\tconst queue = this.pendingPackets.get(subscriptionId);\n\t\t\tconst packetData = data as { data: ArrayBuffer; remoteAddress: string; remotePort: number };\n\t\t\tif (queue) {\n\t\t\t\tqueue.push({\n\t\t\t\t\tdata: packetData.data,\n\t\t\t\t\tremoteAddress: packetData.remoteAddress,\n\t\t\t\t\tremotePort: packetData.remotePort,\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// After handshake, forward to callback\n\t\tconst packetData = data as { data: ArrayBuffer };\n\t\tconst metadata: PacketMetadata = {\n\t\t\tsourceAddress: subscription.relay?.address || \"unknown\",\n\t\t\tsourcePort: subscription.relay?.port || 0,\n\t\t\ttransport: \"tunnel\",\n\t\t\ttimestamp: performance.now(),\n\t\t};\n\n\t\tsubscription.config.onPacket(packetData.data, metadata);\n\t}\n\n\t/**\n\t * Handle socket error from service worker\n\t */\n\tprivate handleSocketError(message: { subscriptionId?: string; error?: string }): void {\n\t\tconst { subscriptionId, error } = message;\n\t\tif (!subscriptionId) return;\n\n\t\tconst subscription = this.subscriptions.get(subscriptionId);\n\t\tif (!subscription) {\n\t\t\tconsole.warn(`[AMT] Received error for unknown subscription: ${subscriptionId}`);\n\t\t\treturn;\n\t\t}\n\n\t\tsubscription.config.onError(new Error(error || \"Unknown error\"));\n\t}\n\n\t/**\n\t * Cleanup on destroy\n\t */\n\tdestroy(): void {\n\t\tif (this.messageHandler && typeof navigator !== \"undefined\" && navigator.serviceWorker) {\n\t\t\tnavigator.serviceWorker.removeEventListener(\"message\", this.messageHandler);\n\t\t}\n\n\t\tconst handles = Array.from(this.subscriptions.keys());\n\t\tfor (const handle of handles) {\n\t\t\tthis.unsubscribe(handle).catch((err) => {\n\t\t\t\tconsole.error(`[AMT] Error unsubscribing ${handle}:`, err);\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Get key string for HashMap\n\t */\n\tprivate getKeyString(key: GroupKey): string {\n\t\tconst source = key.source ? `@${key.source}` : \"\";\n\t\treturn `${key.group}:${key.port}${source}`;\n\t}\n}\n\n/**\n * AMT Gateway Client (Direct Sockets based)\n *\n * Connects to an AMT relay to receive multicast data via unicast tunnel.\n * Implements the gateway side of RFC 7450.\n * Supports multiple (S,G) groups over a single AMT tunnel.\n */\nexport class AMTGateway {\n\tprivate relayAddress: string;\n\tprivate relayPort: number;\n\tprivate groups = new Map<\n\t\tstring,\n\t\t{\n\t\t\tgroupPort: number;\n\t\t\tonPacket: ((packet: AMTPacketInfo) => void) | null;\n\t\t\tjoinedAt: number;\n\t\t}\n\t>();\n\n\t// Callbacks\n\tprivate onPacket: (packet: AMTPacketInfo) => void;\n\tprivate onStateChange: (newState: GatewayState, oldState: GatewayState) => void;\n\tprivate onError: (error: Error) => void;\n\tprivate onLog: (message: string) => void;\n\n\t// State\n\tprivate state: GatewayState = GATEWAY_STATES.IDLE;\n\tprivate nonce: Uint8Array | null = null;\n\tprivate responseMAC: Uint8Array | null = null;\n\tprivate relayConfirmedAddress: string | null = null;\n\tprivate lastDataTime = 0;\n\n\t// Stats\n\tprivate stats = {\n\t\tpacketsReceived: 0,\n\t\tbytesReceived: 0,\n\t\tqueriesReceived: 0,\n\t\tupdatesSet: 0,\n\t};\n\n\tconstructor(options: AMTGatewayOptions) {\n\t\tthis.relayAddress = options.relayAddress;\n\t\tthis.relayPort = options.relayPort || DEFAULT_AMT_RELAY_PORT;\n\n\t\tthis.onPacket = options.onPacket || (() => {});\n\t\tthis.onStateChange = options.onStateChange || (() => {});\n\t\tthis.onError = options.onError || ((err) => console.error(\"AMT Error:\", err));\n\t\tthis.onLog = options.onLog || ((msg) => console.log(\"AMT:\", msg));\n\t}\n\n\t/**\n\t * Generate random nonce\n\t */\n\tprivate generateNonce(): Uint8Array {\n\t\tconst nonce = new Uint8Array(4);\n\t\tcrypto.getRandomValues(nonce);\n\t\treturn nonce;\n\t}\n\n\t/**\n\t * Set gateway state\n\t */\n\tprivate setState(newState: GatewayState): void {\n\t\tconst oldState = this.state;\n\t\tthis.state = newState;\n\t\tthis.onLog(`State: ${oldState} -> ${newState}`);\n\t\tthis.onStateChange(newState, oldState);\n\t}\n\n\t/**\n\t * Join a multicast group\n\t */\n\tasync joinGroup(\n\t\tgroupAddress: string,\n\t\tgroupPort: number,\n\t\tsourceAddress: string | null = null,\n\t\tonPacket: ((packet: AMTPacketInfo) => void) | null = null,\n\t): Promise<string> {\n\t\tconst groupKey = `${sourceAddress || \"*\"},${groupAddress}`;\n\n\t\tif (this.groups.has(groupKey)) {\n\t\t\tthis.onLog(`Group already joined: ${groupKey}`);\n\t\t\treturn groupKey;\n\t\t}\n\n\t\tthis.groups.set(groupKey, {\n\t\t\tgroupPort,\n\t\t\tonPacket,\n\t\t\tjoinedAt: Date.now(),\n\t\t});\n\n\t\tthis.onLog(`Joined group: ${groupKey} (port ${groupPort})`);\n\t\treturn groupKey;\n\t}\n\n\t/**\n\t * Leave a multicast group\n\t */\n\tasync leaveGroup(groupAddress: string, sourceAddress: string | null = null): Promise<boolean> {\n\t\tconst groupKey = `${sourceAddress || \"*\"},${groupAddress}`;\n\n\t\tif (!this.groups.has(groupKey)) {\n\t\t\tthis.onLog(`Group not found: ${groupKey}`);\n\t\t\treturn false;\n\t\t}\n\n\t\tthis.groups.delete(groupKey);\n\t\tthis.onLog(`Left group: ${groupKey} (${this.groups.size} remaining)`);\n\n\t\tif (this.groups.size === 0) {\n\t\t\tthis.onLog(\"No groups remaining - closing AMT tunnel\");\n\t\t\tawait this.close();\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Get list of active groups\n\t */\n\tgetGroups(): Array<{\n\t\tgroupKey: string;\n\t\tgroupAddress: string;\n\t\tsourceAddress: string | null;\n\t\tgroupPort: number;\n\t\tjoinedAt: number;\n\t}> {\n\t\treturn Array.from(this.groups.entries()).map(([key, info]) => {\n\t\t\tconst [source, group] = key.split(\",\");\n\t\t\treturn {\n\t\t\t\tgroupKey: key,\n\t\t\t\tgroupAddress: group,\n\t\t\t\tsourceAddress: source !== \"*\" ? source : null,\n\t\t\t\tgroupPort: info.groupPort,\n\t\t\t\tjoinedAt: info.joinedAt,\n\t\t\t};\n\t\t});\n\t}\n\n\t/**\n\t * Open connection to AMT relay\n\t */\n\tasync open(): Promise<void> {\n\t\tif (this.state !== GATEWAY_STATES.IDLE) {\n\t\t\tthrow new Error(`Cannot open in state: ${this.state}`);\n\t\t}\n\n\t\tthis.setState(GATEWAY_STATES.DISCOVERING);\n\t\tthis.onLog(`Connecting to AMT relay ${this.relayAddress}:${this.relayPort}`);\n\t\tthis.onLog(`Active groups: ${this.groups.size}`);\n\n\t\t// Generate session nonce\n\t\tthis.nonce = this.generateNonce();\n\t\tthis.onLog(`Generated nonce: ${Array.from(this.nonce).map((b) => b.toString(16).padStart(2, \"0\")).join(\"\")}`);\n\n\t\t// Note: Actual UDPSocket connection would go here\n\t\t// This is a simplified version showing the state machine\n\t}\n\n\t/**\n\t * Close connection\n\t */\n\tasync close(): Promise<void> {\n\t\tif (this.state === GATEWAY_STATES.CLOSED) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.setState(GATEWAY_STATES.LEAVING);\n\n\t\t// Clear all groups\n\t\tthis.groups.clear();\n\n\t\tthis.setState(GATEWAY_STATES.CLOSED);\n\t\tthis.onLog(\"AMT Gateway closed\");\n\t}\n\n\t/**\n\t * Get current statistics\n\t */\n\tgetStats(): typeof this.stats {\n\t\treturn { ...this.stats };\n\t}\n\n\t/**\n\t * Get current state\n\t */\n\tgetState(): GatewayState {\n\t\treturn this.state;\n\t}\n\n\t/**\n\t * Check if gateway is active and receiving data\n\t */\n\tisActive(): boolean {\n\t\treturn this.state === GATEWAY_STATES.ACTIVE;\n\t}\n}\n\n// Export singleton manager instance\nexport const amtGatewayManager = new AMTGatewayManager();\n", "/**\n * Strict v2 multicast delivery catalog: the DASH/HLS/MMTP routes a receiver\n * joins, their SSM endpoints, FEC geometry and MMTP packet-ID bindings.\n *\n * Moved from moqtail-private `libs/moqtail-ts/src/model/catalog/delivery.ts`\n * (BLO-47003) so every player reads one contract. Two rules are stricter than\n * the copy it replaces, both libmmt invariants rather than local choices:\n * `fec.blockProfile` is required and must be `iso-ssbg1-v1` (BLO-35597), and a\n * DASH route's `mmtp.packetId` is refused when it is the signaling flow 0\n * (`isMulticastTrackPacketId`).\n */\nimport { parseFecBlockProfile, type FecBlockProfile } from '@blockcast/mmt-base'\nimport { isMulticastTrackPacketId } from '@blockcast/mmt-transport'\n\nexport type DeliveryPackaging = 'mmtp' | 'dash' | 'hls'\nexport type DeliveryContentType = 'video' | 'audio' | 'text'\n\nexport interface DeliveryEndpoint {\n source: string\n group: string\n port: number\n}\n\nexport interface DeliveryPolicy {\n bufferWindowSegments: number\n requestTimeoutMs: number\n missPolicy: 'fail'\n}\n\nexport interface DeliveryObjectMapping {\n namespace: string\n initObject: string\n mediaObjectTemplate: string\n}\n\nexport interface DeliveryFec {\n K: number\n /** Repair symbols per source block. Zero means source-only delivery. */\n P: number\n T: number\n /** Full FEC block span in milliseconds (draft-ramadan-moq-fec \u00A75.1). */\n interleaveDepthMs: number\n /**\n * The catalog `fec.blockProfile` (BLO-35597). Required and never\n * defaulted; `parseFecBlockProfile` refuses anything but `iso-ssbg1-v1`,\n * including the retired `k-padded-v1`.\n */\n blockProfile: FecBlockProfile\n}\n\nexport interface DeliveryRouteBase {\n id: string\n contentType: DeliveryContentType\n endpointRef: string\n initUri: string\n mediaUriTemplate: string\n objects: DeliveryObjectMapping\n fec: DeliveryFec\n}\n\nexport interface DashDeliveryRoute extends DeliveryRouteBase {\n representationId: string\n /**\n * Authoritative MMTP binding used to assemble the DASH objects carried by\n * this route. The current source accepts MFU-mode MMTP only; the init MPU is\n * the representation init segment. MMTP-backed DASH URI and object templates\n * contain exactly one $Number$ and may also contain $RepresentationID$;\n * $Time$ is rejected because the catalog has no publisher-authored first-time\n * anchor. Publishers explicitly anchor MMTP MPU sequence numbers to DASH\n * $Number$ values so receivers never infer object boundaries from cadence.\n */\n mmtp: {\n trackName: string\n packetId: number\n mode: 'mfu'\n codec: string\n /** fMP4 media timescale used by Shaka's MMTP transmuxer. */\n timescale: number\n /** Exact duration of one MFU in that timescale, from publisher timing. */\n mediaUnitDurationTicks: number\n /** Exact MFU count carried by each numbered DASH media object. */\n mediaUnitsPerObject: number\n /** First publisher MMTP MPU sequence represented by this route. */\n firstMpuSequenceNumber: number\n /** DASH $Number$ containing firstMpuSequenceNumber. */\n firstObjectNumber: number\n /** Consecutive MMTP MPU sequence numbers carried by each DASH object. */\n mpuSequencesPerObject: number\n /** Optional reliable repair-track name under objects.namespace. */\n repairTrack?: string\n }\n}\n\nexport interface HlsDeliveryRoute extends DeliveryRouteBase {\n variantId: string\n trackId: string\n}\n\nexport interface MmtpDeliveryRoute extends DeliveryRouteBase {\n trackName: string\n}\n\nexport interface DashDeliveryConfig {\n manifestUrl: string\n routes: DashDeliveryRoute[]\n}\n\nexport interface HlsDeliveryConfig {\n manifestUrl: string\n routes: HlsDeliveryRoute[]\n}\n\nexport interface MmtpDeliveryConfig {\n routes: MmtpDeliveryRoute[]\n}\n\nexport interface DeliveryMulticastBase {\n endpoints: Record<string, DeliveryEndpoint>\n deliveryPolicy: DeliveryPolicy\n}\n\nexport type DeliveryMulticastV2 = DeliveryMulticastBase &\n (\n | { packaging: 'dash'; dash: DashDeliveryConfig }\n | { packaging: 'hls'; hls: HlsDeliveryConfig }\n | { packaging: 'mmtp'; mmtp: MmtpDeliveryConfig }\n )\n\nexport interface DeliveryCatalogV2 {\n schemaVersion: 2\n channelId: string\n multicast: DeliveryMulticastV2\n}\n\nconst TEMPLATE_VARIABLE = /\\$([A-Za-z][A-Za-z0-9]*)\\$/g\nconst ALLOWED_TEMPLATE_VARIABLES = new Set(['Number', 'Time', 'RepresentationID', 'VariantID', 'TrackID'])\n\nfunction object(value: unknown, path: string): Record<string, unknown> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new Error(`${path} must be an object`)\n }\n return value as Record<string, unknown>\n}\n\nfunction string(value: unknown, path: string): string {\n if (typeof value !== 'string' || value.trim().length === 0) {\n throw new Error(`${path} must be a non-empty string`)\n }\n return value\n}\n\nfunction positiveInteger(value: unknown, path: string): number {\n if (typeof value !== 'number' || !Number.isInteger(value) || value <= 0) {\n throw new Error(`${path} must be a positive integer`)\n }\n return value\n}\n\nfunction nonNegativeInteger(value: unknown, path: string): number {\n if (typeof value !== 'number' || !Number.isInteger(value) || value < 0) {\n throw new Error(`${path} must be a non-negative integer`)\n }\n return value\n}\n\nfunction nonNegativeSafeInteger(value: unknown, path: string): number {\n const parsed = nonNegativeInteger(value, path)\n if (!Number.isSafeInteger(parsed)) throw new Error(`${path} must be a safe integer`)\n return parsed\n}\n\nfunction mpuSequenceNumber(value: unknown, path: string): number {\n const parsed = nonNegativeSafeInteger(value, path)\n if (parsed > 0xffffffff) throw new Error(`${path} must be between 0 and 4294967295`)\n return parsed\n}\n\nfunction port(value: unknown, path: string): number {\n const parsed = positiveInteger(value, path)\n if (parsed > 65535) throw new Error(`${path} must be between 1 and 65535`)\n return parsed\n}\n\nfunction httpUrl(value: unknown, path: string): string {\n const raw = string(value, path)\n let parsed: URL\n try {\n parsed = new URL(raw)\n } catch {\n throw new Error(`${path} must be an absolute HTTP(S) URL`)\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n throw new Error(`${path} must be an absolute HTTP(S) URL`)\n }\n return raw\n}\n\nfunction templateVariables(template: string, path: string): Set<string> {\n const variables = new Set<string>()\n for (const match of template.matchAll(TEMPLATE_VARIABLE)) {\n const variable = match[1]\n if (variable === undefined || !ALLOWED_TEMPLATE_VARIABLES.has(variable)) {\n throw new Error(`${path} contains unsupported template variable $${variable ?? ''}$`)\n }\n variables.add(variable)\n }\n if (template.includes('$') && template.replace(TEMPLATE_VARIABLE, '').includes('$')) {\n throw new Error(`${path} contains a malformed template variable`)\n }\n return variables\n}\n\nfunction validateTemplatePair(mediaUriTemplate: string, mediaObjectTemplate: string, path: string): void {\n const uriVariables = templateVariables(mediaUriTemplate, `${path}.mediaUriTemplate`)\n const objectVariables = templateVariables(mediaObjectTemplate, `${path}.objects.mediaObjectTemplate`)\n if (uriVariables.size === 0) {\n throw new Error(`${path}.mediaUriTemplate must contain a segment template variable`)\n }\n if (\n uriVariables.size !== objectVariables.size ||\n [...uriVariables].some((variable) => !objectVariables.has(variable))\n ) {\n throw new Error(`${path} URI and object templates must use the same variables`)\n }\n}\n\nfunction validateMmtpDashTemplate(template: string, path: string): void {\n const variables = [...template.matchAll(TEMPLATE_VARIABLE)].map((match) => match[1])\n const unsupported = variables.find((variable) => variable !== 'Number' && variable !== 'RepresentationID')\n if (unsupported !== undefined) {\n throw new Error(`${path} contains unsupported MMTP-backed DASH template variable $${unsupported}$`)\n }\n if (variables.filter((variable) => variable === 'Number').length !== 1) {\n throw new Error(`${path} must contain exactly one $Number$ for MMTP-backed DASH`)\n }\n}\n\nfunction validateMmtpDashTemplates(route: DeliveryRouteBase, path: string): void {\n validateMmtpDashTemplate(route.mediaUriTemplate, `${path}.mediaUriTemplate`)\n validateMmtpDashTemplate(route.objects.mediaObjectTemplate, `${path}.objects.mediaObjectTemplate`)\n}\n\nfunction validateEndpoint(value: unknown, path: string): DeliveryEndpoint {\n const endpoint = object(value, path)\n return {\n source: string(endpoint['source'], `${path}.source`),\n group: string(endpoint['group'], `${path}.group`),\n port: port(endpoint['port'], `${path}.port`),\n }\n}\n\nfunction validateEndpoints(value: unknown): Record<string, DeliveryEndpoint> {\n const endpoints = object(value, 'multicast.endpoints')\n const entries = Object.entries(endpoints)\n if (entries.length === 0) throw new Error('multicast.endpoints must not be empty')\n return Object.fromEntries(\n entries.map(([id, endpoint]) => [\n string(id, 'multicast endpoint id'),\n validateEndpoint(endpoint, `multicast.endpoints.${id}`),\n ]),\n )\n}\n\nfunction validateDeliveryPolicy(value: unknown): DeliveryPolicy {\n const policy = object(value, 'multicast.deliveryPolicy')\n const missPolicy = policy['missPolicy']\n if (missPolicy !== 'fail') {\n throw new Error(\"multicast.deliveryPolicy.missPolicy must be 'fail'\")\n }\n return {\n bufferWindowSegments: positiveInteger(\n policy['bufferWindowSegments'],\n 'multicast.deliveryPolicy.bufferWindowSegments',\n ),\n requestTimeoutMs: positiveInteger(policy['requestTimeoutMs'], 'multicast.deliveryPolicy.requestTimeoutMs'),\n missPolicy,\n }\n}\n\nfunction validateRouteBase(\n value: unknown,\n path: string,\n endpoints: Record<string, DeliveryEndpoint>,\n): DeliveryRouteBase {\n const route = object(value, path)\n const endpointRef = string(route['endpointRef'], `${path}.endpointRef`)\n if (!(endpointRef in endpoints)) throw new Error(`${path}.endpointRef does not resolve: ${endpointRef}`)\n\n const objects = object(route['objects'], `${path}.objects`)\n const fec = object(route['fec'], `${path}.fec`)\n if ('interleaveDepth' in fec || 'interleaveMs' in fec) {\n throw new Error(`${path}.fec.interleaveDepthMs is the only supported interleave field`)\n }\n const mediaUriTemplate = httpUrl(route['mediaUriTemplate'], `${path}.mediaUriTemplate`)\n const mediaObjectTemplate = string(objects['mediaObjectTemplate'], `${path}.objects.mediaObjectTemplate`)\n validateTemplatePair(mediaUriTemplate, mediaObjectTemplate, path)\n\n return {\n id: string(route['id'], `${path}.id`),\n contentType: validateContentType(route['contentType'], `${path}.contentType`),\n endpointRef,\n initUri: httpUrl(route['initUri'], `${path}.initUri`),\n mediaUriTemplate,\n objects: {\n namespace: string(objects['namespace'], `${path}.objects.namespace`),\n initObject: string(objects['initObject'], `${path}.objects.initObject`),\n mediaObjectTemplate,\n },\n fec: {\n K: positiveInteger(fec['K'], `${path}.fec.K`),\n P: nonNegativeInteger(fec['P'], `${path}.fec.P`),\n T: positiveInteger(fec['T'], `${path}.fec.T`),\n interleaveDepthMs: positiveInteger(fec['interleaveDepthMs'], `${path}.fec.interleaveDepthMs`),\n blockProfile: parseFecBlockProfile(fec['blockProfile'], `${path}.fec.blockProfile`),\n },\n }\n}\n\nfunction validateContentType(value: unknown, path: string): DeliveryContentType {\n if (value !== 'video' && value !== 'audio' && value !== 'text') {\n throw new Error(`${path} must be 'video', 'audio', or 'text'`)\n }\n return value\n}\n\nfunction templateShape(template: string): string {\n return template.replace(TEMPLATE_VARIABLE, '$variable$')\n}\n\nfunction validateRouteUniqueness(\n routes: DeliveryRouteBase[],\n selectorKeys: string[],\n rawRoutes: Record<string, unknown>[],\n): void {\n const ids = new Set<string>()\n const selectors = new Set<string>()\n const initUris = new Set<string>()\n const mediaShapes = new Set<string>()\n routes.forEach((route, index) => {\n if (ids.has(route.id)) throw new Error(`duplicate route id: ${route.id}`)\n ids.add(route.id)\n\n const selector = selectorKeys.map((key) => String(rawRoutes[index]?.[key])).join('\\u0000')\n if (selectors.has(selector)) throw new Error(`duplicate route selector: ${selectorKeys.join(', ')}`)\n selectors.add(selector)\n\n if (initUris.has(route.initUri)) throw new Error(`ambiguous init URI mapping: ${route.initUri}`)\n initUris.add(route.initUri)\n\n const mediaShape = templateShape(route.mediaUriTemplate)\n if (mediaShapes.has(mediaShape)) {\n throw new Error(`ambiguous media URI mapping: ${route.mediaUriTemplate}`)\n }\n mediaShapes.add(mediaShape)\n })\n}\n\nfunction validateRoutes(value: unknown, kind: 'dash', endpoints: Record<string, DeliveryEndpoint>): DashDeliveryRoute[]\nfunction validateRoutes(value: unknown, kind: 'hls', endpoints: Record<string, DeliveryEndpoint>): HlsDeliveryRoute[]\nfunction validateRoutes(value: unknown, kind: 'mmtp', endpoints: Record<string, DeliveryEndpoint>): MmtpDeliveryRoute[]\nfunction validateRoutes(\n value: unknown,\n kind: DeliveryPackaging,\n endpoints: Record<string, DeliveryEndpoint>,\n): (DashDeliveryRoute | HlsDeliveryRoute | MmtpDeliveryRoute)[] {\n if (!Array.isArray(value) || value.length === 0) throw new Error(`multicast.${kind}.routes must not be empty`)\n const rawRoutes = value.map((route, index) => object(route, `multicast.${kind}.routes[${index}]`))\n const routes = rawRoutes.map((route, index) => {\n const path = `multicast.${kind}.routes[${index}]`\n const base = validateRouteBase(route, path, endpoints)\n if (kind === 'dash') {\n validateMmtpDashTemplates(base, path)\n const mmtp = object(route['mmtp'], `${path}.mmtp`)\n if (mmtp['mode'] !== 'mfu') throw new Error(`${path}.mmtp.mode must be 'mfu'`)\n const mode = 'mfu' as const\n const packetId = mmtp['packetId']\n if (!isMulticastTrackPacketId(packetId)) {\n throw new Error(`${path}.mmtp.packetId must be an integer from 1 to 65535 (0 is the MMTP signaling flow)`)\n }\n const repairTrack = mmtp['repairTrack']\n if (repairTrack !== undefined && (typeof repairTrack !== 'string' || repairTrack.trim().length === 0)) {\n throw new Error(`${path}.mmtp.repairTrack must be a non-empty string when provided`)\n }\n const mediaUnitsPerObject = positiveInteger(mmtp['mediaUnitsPerObject'], `${path}.mmtp.mediaUnitsPerObject`)\n const firstMpuSequenceNumber = mpuSequenceNumber(\n mmtp['firstMpuSequenceNumber'],\n `${path}.mmtp.firstMpuSequenceNumber`,\n )\n const firstObjectNumber = nonNegativeSafeInteger(mmtp['firstObjectNumber'], `${path}.mmtp.firstObjectNumber`)\n const mpuSequencesPerObject = positiveInteger(mmtp['mpuSequencesPerObject'], `${path}.mmtp.mpuSequencesPerObject`)\n if (mpuSequencesPerObject > mediaUnitsPerObject) {\n throw new Error(`${path}.mmtp.mpuSequencesPerObject must not exceed mediaUnitsPerObject`)\n }\n if (firstMpuSequenceNumber + mpuSequencesPerObject - 1 > 0xffffffff) {\n throw new Error(`${path}.mmtp first object exceeds the MMTP MPU sequence-number range`)\n }\n return {\n ...base,\n representationId: string(route['representationId'], `${path}.representationId`),\n mmtp: {\n trackName: string(mmtp['trackName'], `${path}.mmtp.trackName`),\n packetId,\n mode,\n codec: string(mmtp['codec'], `${path}.mmtp.codec`),\n timescale: positiveInteger(mmtp['timescale'], `${path}.mmtp.timescale`),\n mediaUnitDurationTicks: positiveInteger(\n mmtp['mediaUnitDurationTicks'],\n `${path}.mmtp.mediaUnitDurationTicks`,\n ),\n mediaUnitsPerObject,\n firstMpuSequenceNumber,\n firstObjectNumber,\n mpuSequencesPerObject,\n ...(repairTrack === undefined ? {} : { repairTrack }),\n },\n }\n }\n if (kind === 'hls') {\n return {\n ...base,\n variantId: string(route['variantId'], `${path}.variantId`),\n trackId: string(route['trackId'], `${path}.trackId`),\n }\n }\n return { ...base, trackName: string(route['trackName'], `${path}.trackName`) }\n })\n validateRouteUniqueness(\n routes,\n kind === 'dash' ? ['representationId', 'contentType'] : kind === 'hls' ? ['variantId', 'trackId'] : ['trackName'],\n rawRoutes,\n )\n return routes\n}\n\nfunction validateV2Catalog(value: unknown): DeliveryCatalogV2 {\n const catalog = object(value, 'catalog')\n if (catalog['schemaVersion'] !== 2) throw new Error('catalog.schemaVersion must be 2')\n const multicast = object(catalog['multicast'], 'multicast')\n const packaging = multicast['packaging']\n if (packaging !== 'dash' && packaging !== 'hls' && packaging !== 'mmtp') {\n throw new Error(\"multicast.packaging must be 'dash', 'hls', or 'mmtp'\")\n }\n const endpoints = validateEndpoints(multicast['endpoints'])\n const deliveryPolicy = validateDeliveryPolicy(multicast['deliveryPolicy'])\n const common = {\n schemaVersion: 2 as const,\n channelId: string(catalog['channelId'], 'catalog.channelId'),\n }\n\n if (packaging === 'dash') {\n const dash = object(multicast['dash'], 'multicast.dash')\n return {\n ...common,\n multicast: {\n packaging,\n endpoints,\n deliveryPolicy,\n dash: {\n manifestUrl: httpUrl(dash['manifestUrl'], 'multicast.dash.manifestUrl'),\n routes: validateRoutes(dash['routes'], packaging, endpoints),\n },\n },\n }\n }\n if (packaging === 'hls') {\n const hls = object(multicast['hls'], 'multicast.hls')\n return {\n ...common,\n multicast: {\n packaging,\n endpoints,\n deliveryPolicy,\n hls: {\n manifestUrl: httpUrl(hls['manifestUrl'], 'multicast.hls.manifestUrl'),\n routes: validateRoutes(hls['routes'], packaging, endpoints),\n },\n },\n }\n }\n const mmtp = object(multicast['mmtp'], 'multicast.mmtp')\n return {\n ...common,\n multicast: {\n packaging,\n endpoints,\n deliveryPolicy,\n mmtp: { routes: validateRoutes(mmtp['routes'], packaging, endpoints) },\n },\n }\n}\n\n/** Parse and normalize the strict v2 delivery-catalog contract. */\nexport function normalizeDeliveryCatalog(value: unknown): DeliveryCatalogV2 {\n const catalog = object(value, 'catalog')\n if (catalog['schemaVersion'] === 2) return validateV2Catalog(catalog)\n if (catalog['schemaVersion'] === undefined) throw new Error('catalog.schemaVersion is required')\n throw new Error(`unsupported catalog.schemaVersion: ${String(catalog['schemaVersion'])}`)\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["MoqFecAlgorithm", "MPU_HEADER_SIZE", "MPU_LENGTH_FIELD_SIZE", "MPU_HEADER_REMAINDER_SIZE", "MPU_HEADER_SIZE", "NTP_SHORT_BITS", "NTP_SHORT_MODULUS", "NTP_SHORT_HALF_RANGE", "FEC_BLOCK_PROFILES", "port", "entry", "object", "packetData"]
7
+ }