@blockcast/mmt-container 0.5.0 → 1.0.0-main.3cb7359ea316

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 (136) hide show
  1. package/README.md +21 -16
  2. package/dist/browser-runtime.js +1024 -1078
  3. package/dist/browser-runtime.js.map +4 -4
  4. package/dist/carp-catalog.d.ts +33 -87
  5. package/dist/carp-catalog.d.ts.map +1 -1
  6. package/dist/carp-catalog.js +12 -434
  7. package/dist/carp-catalog.js.map +4 -4
  8. package/dist/catalog-fec-params.d.ts +48 -161
  9. package/dist/catalog-fec-params.d.ts.map +1 -1
  10. package/dist/catalog-fec-params.js +567 -592
  11. package/dist/catalog-fec-params.js.map +4 -4
  12. package/dist/catalog-network-source.js +6 -2
  13. package/dist/catalog-network-source.js.map +3 -3
  14. package/dist/delivery-catalog.js +12 -4
  15. package/dist/delivery-catalog.js.map +4 -4
  16. package/dist/delta-fallback-config.d.ts +21 -26
  17. package/dist/delta-fallback-config.d.ts.map +1 -1
  18. package/dist/delta-fallback-config.js +582 -24
  19. package/dist/delta-fallback-config.js.map +4 -4
  20. package/dist/fec-manager.js +6 -2
  21. package/dist/fec-manager.js.map +3 -3
  22. package/dist/fec-state.d.ts +5 -43
  23. package/dist/fec-state.d.ts.map +1 -1
  24. package/dist/fec-state.js +529 -34
  25. package/dist/fec-state.js.map +4 -4
  26. package/dist/fec-worker-setup.js +6 -2
  27. package/dist/fec-worker-setup.js.map +3 -3
  28. package/dist/held-layer-admission.js +6 -2
  29. package/dist/held-layer-admission.js.map +3 -3
  30. package/dist/index.d.ts +3 -3
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +5 -39
  33. package/dist/index.js.map +2 -2
  34. package/dist/iso-block-locator.js +6 -2
  35. package/dist/iso-block-locator.js.map +3 -3
  36. package/dist/mfu-reassembler.js +6 -2
  37. package/dist/mfu-reassembler.js.map +3 -3
  38. package/dist/mmtp-container-parser.js +6 -2
  39. package/dist/mmtp-container-parser.js.map +3 -3
  40. package/dist/mmtp-container-parser.no-modules.js +551 -452
  41. package/dist/mmtp-container-parser.no-modules.js.map +4 -4
  42. package/dist/mmtp-decode-pipeline.js +6 -2
  43. package/dist/mmtp-decode-pipeline.js.map +3 -3
  44. package/dist/mmtp-fec-session.d.ts +7 -6
  45. package/dist/mmtp-fec-session.d.ts.map +1 -1
  46. package/dist/mmtp-fec-session.js +13 -9
  47. package/dist/mmtp-fec-session.js.map +3 -3
  48. package/dist/mmtp-router.d.ts +7 -16
  49. package/dist/mmtp-router.d.ts.map +1 -1
  50. package/dist/mmtp-router.js +19 -29
  51. package/dist/mmtp-router.js.map +3 -3
  52. package/dist/multicast-auth-catalog.js +7 -3
  53. package/dist/multicast-auth-catalog.js.map +3 -3
  54. package/dist/multicast-input.js +7 -3
  55. package/dist/multicast-input.js.map +3 -3
  56. package/dist/multicast-manager.d.ts +29 -32
  57. package/dist/multicast-manager.d.ts.map +1 -1
  58. package/dist/multicast-manager.js +682 -253
  59. package/dist/multicast-manager.js.map +4 -4
  60. package/dist/multicast-transport.d.ts +23 -2
  61. package/dist/multicast-transport.d.ts.map +1 -1
  62. package/dist/multicast-transport.js +57 -16
  63. package/dist/multicast-transport.js.map +4 -4
  64. package/dist/multicast-unwrap.js +6 -2
  65. package/dist/multicast-unwrap.js.map +3 -3
  66. package/dist/no-modules.js +551 -452
  67. package/dist/no-modules.js.map +4 -4
  68. package/dist/phase14-catalog-override.d.ts +10 -5
  69. package/dist/phase14-catalog-override.d.ts.map +1 -1
  70. package/dist/phase14-catalog-override.js +35 -11
  71. package/dist/phase14-catalog-override.js.map +2 -2
  72. package/dist/repair-fetch.d.ts +3 -1
  73. package/dist/repair-fetch.d.ts.map +1 -1
  74. package/dist/repair-fetch.js +16 -3
  75. package/dist/repair-fetch.js.map +3 -3
  76. package/dist/repair-layers.d.ts +0 -123
  77. package/dist/repair-layers.d.ts.map +1 -1
  78. package/dist/repair-layers.js +1 -349
  79. package/dist/repair-layers.js.map +3 -3
  80. package/dist/signaling.js +6 -2
  81. package/dist/signaling.js.map +3 -3
  82. package/dist/timestamp-codec.js +6 -2
  83. package/dist/timestamp-codec.js.map +3 -3
  84. package/dist/wasm-mfu-reassembler.js +6 -2
  85. package/dist/wasm-mfu-reassembler.js.map +3 -3
  86. package/dist/wasm-mmtp-container-parser.js +6 -2
  87. package/dist/wasm-mmtp-container-parser.js.map +3 -3
  88. package/package.json +9 -5
  89. package/schemas/catalog.schema.json +53 -58
  90. package/src/carp-catalog.ts +39 -229
  91. package/src/catalog-fec-params.ts +130 -493
  92. package/src/delta-fallback-config.ts +63 -50
  93. package/src/fec-state.ts +7 -62
  94. package/src/index.ts +10 -30
  95. package/src/mmtp-fec-session.ts +11 -10
  96. package/src/mmtp-router.ts +14 -29
  97. package/src/multicast-manager.ts +89 -121
  98. package/src/multicast-transport.ts +65 -12
  99. package/src/phase14-catalog-override.ts +67 -16
  100. package/src/repair-fetch.ts +27 -3
  101. package/src/repair-layers.ts +7 -520
  102. package/test-vectors/catalog/README.md +39 -67
  103. package/test-vectors/catalog/fec-01-runtime/README.md +15 -0
  104. package/test-vectors/catalog/{negative/fec-block-profile-retired.json → fec-01-runtime/fec-iso-ssbg1.json} +1 -1
  105. package/test-vectors/catalog/fec-01-runtime/fec-layered-repair.json +106 -0
  106. package/test-vectors/catalog/{negative/fec-block-profile-unknown.json → fec-01-runtime/fec-multicast.json} +2 -1
  107. package/test-vectors/catalog/fec-01-runtime/multicast-native-ssm-no-network-source.json +44 -0
  108. package/test-vectors/catalog/negative/fec-block-profile-missing.json +24 -11
  109. package/test-vectors/catalog/negative/fec-iso-symbol-size-below-floor.json +13 -8
  110. package/test-vectors/catalog/negative/fec-reed-solomon-iso-symbol-size-13.json +13 -8
  111. package/test-vectors/catalog/negative/fec-repair-layer-geometry.json +20 -10
  112. package/test-vectors/catalog/negative/fec-repair-layer-missing-symbols.json +20 -10
  113. package/test-vectors/catalog/negative/fec-repair-overlay-source-symbols-zero.json +25 -17
  114. package/test-vectors/catalog/negative/fec-repair-symbols-zero.json +13 -8
  115. package/test-vectors/catalog/negative/fec-source-symbols-zero.json +13 -8
  116. package/test-vectors/catalog/negative/fec-symbol-size-zero.json +13 -8
  117. package/test-vectors/catalog/negative/raptorq-source-symbols-over-max.json +18 -9
  118. package/test-vectors/catalog/negative/raptorq-unaligned-symbol.json +13 -8
  119. package/test-vectors/catalog/positive/fec-algorithm-unimplemented.json +14 -9
  120. package/test-vectors/catalog/positive/fec-iso-ssbg1.json +25 -12
  121. package/test-vectors/catalog/positive/fec-layered-repair.json +79 -35
  122. package/test-vectors/catalog/positive/fec-multicast.json +26 -13
  123. package/test-vectors/catalog/positive/fec-reed-solomon-layered.json +29 -18
  124. package/test-vectors/catalog/positive/multicast-draft01-4-1-example.json +28 -20
  125. package/test-vectors/catalog/positive/multicast-draft01-7-2-6-example.json +14 -10
  126. package/test-vectors/catalog/positive/multicast-native-ssm-no-network-source.json +22 -12
  127. package/test-vectors/catalog/section-codes.json +13 -22
  128. package/test-vectors/catalog-timing.json +2 -2
  129. package/test-vectors/catalog-validation.json +30 -243
  130. package/test-vectors/catalog/negative/fec-enhancement-repair-removed.json +0 -28
  131. package/test-vectors/catalog/negative/fec-reed-solomon-k-plus-p-over-255.json +0 -39
  132. package/test-vectors/catalog/negative/fec-reed-solomon-overlay-over-255.json +0 -40
  133. package/test-vectors/catalog/negative/fec-repair-multiple-depends.json +0 -32
  134. package/test-vectors/catalog/negative/fec-repair-overlay-with-layer.json +0 -34
  135. package/test-vectors/catalog/negative/fec-repair-priority-out-of-band.json +0 -24
  136. package/test-vectors/catalog/negative/fec-repair-source-symbols-without-scope.json +0 -33
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockcast/mmt-container",
3
- "version": "0.5.0",
3
+ "version": "1.0.0-main.3cb7359ea316",
4
4
  "description": "MMTP/LOC container parsing for MMT over MoQ and SSM",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -26,6 +26,10 @@
26
26
  "import": "./dist/codec-utils.js",
27
27
  "types": "./dist/codec-utils.d.ts"
28
28
  },
29
+ "./fec-catalog": {
30
+ "import": "./dist/fec-catalog.js",
31
+ "types": "./dist/fec-catalog.d.ts"
32
+ },
29
33
  "./fec-manager": {
30
34
  "import": "./dist/fec-manager.js",
31
35
  "types": "./dist/fec-manager.d.ts"
@@ -165,10 +169,10 @@
165
169
  "test:watch": "vitest"
166
170
  },
167
171
  "dependencies": {
168
- "@blockcast/fec-worker": "^0.5.0",
169
- "@blockcast/mmt-base": "^0.5.0",
170
- "@blockcast/mmt-fec": "^0.5.0",
171
- "@blockcast/mmt-manifest-verify": "^0.5.0",
172
+ "@blockcast/fec-worker": "1.0.0-main.3cb7359ea316",
173
+ "@blockcast/mmt-base": "1.0.0-main.3cb7359ea316",
174
+ "@blockcast/mmt-fec": "1.0.0-main.3cb7359ea316",
175
+ "@blockcast/mmt-manifest-verify": "1.0.0-main.3cb7359ea316",
172
176
  "@noble/curves": "^1.9.7",
173
177
  "@noble/hashes": "^1.8.0",
174
178
  "ajv": "^8.17.1",
@@ -65,7 +65,7 @@
65
65
  "properties": {
66
66
  "namespace": { "type": "string", "minLength": 1 },
67
67
  "name": { "type": "string", "minLength": 1 },
68
- "packaging": { "enum": ["mmtp", "fec-repair"] },
68
+ "packaging": { "enum": ["mmtp", "fec-repair", "fec-source"] },
69
69
  "isLive": {
70
70
  "type": "boolean",
71
71
  "default": true,
@@ -106,27 +106,28 @@
106
106
  },
107
107
  "initTrack": { "type": "string", "minLength": 1 },
108
108
  "priority": { "type": "integer", "minimum": 0, "maximum": 255 },
109
+ "source": {
110
+ "type": "string",
111
+ "minLength": 1,
112
+ "description": "fec-repair and fec-source only. Track Name of the source track whose fec array declares the instance this track serves (draft-ramadan-moq-fec-catalog-00 3.2)."
113
+ },
114
+ "instanceId": {
115
+ "type": "integer",
116
+ "minimum": 0,
117
+ "maximum": 255,
118
+ "description": "fec-repair and fec-source only. Instance ID, in the source track's fec array, this track serves; 0 when absent (draft-ramadan-moq-fec-catalog-00 3.2)."
119
+ },
109
120
  "repairLayer": {
110
121
  "type": "integer",
111
122
  "minimum": 0,
112
123
  "maximum": 16777215,
113
- "description": "fec-repair only. Base repair layer index; layer 0 is the track named by the source's fec.repairTrack. Layers 1..n are contiguous per source (draft-ramadan-moq-fec 6.3)."
124
+ "description": "fec-repair only. Repair layer index within the instance; layer 0 is the track named by the instance's repairTrack, layers 1..n are contiguous (draft-ramadan-moq-fec-core-00 10.3 rule 3)."
114
125
  },
115
126
  "repairSymbols": {
116
127
  "type": "integer",
117
128
  "minimum": 1,
118
129
  "maximum": 16777215,
119
- "description": "fec-repair only. Repair symbols per block carried by this layer or overlay (P_i)."
120
- },
121
- "sourceSymbols": {
122
- "type": "integer",
123
- "minimum": 1,
124
- "maximum": 56403,
125
- "description": "fec-repair overlay only. Source symbols per block of the overlay FEC instance (K_overlay)."
126
- },
127
- "scope": {
128
- "const": "keyframe",
129
- "description": "fec-repair only. Marks a keyframe overlay: a separate FEC instance over random access point fragments."
130
+ "description": "fec-repair only. Repair symbols per block carried by this layer (P_i)."
130
131
  },
131
132
  "mmtpMode": { "const": "mfu" },
132
133
  "timescale": { "$ref": "#/$defs/positiveInteger" },
@@ -153,8 +154,8 @@
153
154
  "anyOf": [
154
155
  { "properties": { "repairLayer": true }, "required": ["repairLayer"] },
155
156
  { "properties": { "repairSymbols": true }, "required": ["repairSymbols"] },
156
- { "properties": { "sourceSymbols": true }, "required": ["sourceSymbols"] },
157
- { "properties": { "scope": true }, "required": ["scope"] }
157
+ { "properties": { "source": true }, "required": ["source"] },
158
+ { "properties": { "instanceId": true }, "required": ["instanceId"] }
158
159
  ]
159
160
  },
160
161
  "anyOf": [
@@ -170,22 +171,9 @@
170
171
  }
171
172
  },
172
173
  {
173
- "if": { "properties": { "packaging": { "const": "fec-repair" } }, "required": ["packaging"] },
174
+ "$comment": "draft-ramadan-moq-fec-core-00 10.3 rule 8: a repair or symbol track carries no block geometry. Instance resolution, layer contiguity and priority order are validateFecCatalog's (draft-ramadan-moq-fec-catalog-00 5).",
175
+ "if": { "properties": { "packaging": { "enum": ["fec-repair", "fec-source"] } }, "required": ["packaging"] },
174
176
  "then": {
175
- "properties": {
176
- "priority": {
177
- "type": "integer",
178
- "minimum": 192,
179
- "maximum": 255,
180
- "description": "MoQT-scale delivery priority, lower first. Optional on base layer 0 (240 when absent); required on layers 1..n and overlays."
181
- },
182
- "depends": {
183
- "type": "array",
184
- "minItems": 1,
185
- "maxItems": 1,
186
- "description": "Exactly one source track: an mmtp track carrying an fec object."
187
- }
188
- },
189
177
  "not": {
190
178
  "anyOf": [
191
179
  { "properties": { "mmtpMode": true }, "required": ["mmtpMode"] },
@@ -199,26 +187,22 @@
199
187
  },
200
188
  "allOf": [
201
189
  {
202
- "if": { "properties": { "scope": true }, "required": ["scope"] },
190
+ "if": { "properties": { "packaging": { "const": "fec-source" } }, "required": ["packaging"] },
203
191
  "then": {
204
- "properties": { "depends": true, "sourceSymbols": true, "repairSymbols": true, "priority": true },
205
- "required": ["depends", "sourceSymbols", "repairSymbols", "priority"],
206
- "not": { "properties": { "repairLayer": true }, "required": ["repairLayer"] }
192
+ "not": {
193
+ "anyOf": [
194
+ { "properties": { "repairLayer": true }, "required": ["repairLayer"] },
195
+ { "properties": { "repairSymbols": true }, "required": ["repairSymbols"] }
196
+ ]
197
+ }
207
198
  }
208
199
  },
209
- {
210
- "if": { "properties": { "sourceSymbols": true }, "required": ["sourceSymbols"] },
211
- "then": { "properties": { "scope": true }, "required": ["scope"] }
212
- },
213
200
  {
214
201
  "if": {
215
202
  "properties": { "repairLayer": { "type": "integer", "minimum": 1 } },
216
203
  "required": ["repairLayer"]
217
204
  },
218
- "then": {
219
- "properties": { "depends": true, "repairSymbols": true, "priority": true },
220
- "required": ["depends", "repairSymbols", "priority"]
221
- }
205
+ "then": { "required": ["source", "repairSymbols"] }
222
206
  }
223
207
  ]
224
208
  }
@@ -226,18 +210,25 @@
226
210
  ]
227
211
  },
228
212
  "fec": {
213
+ "type": "array",
214
+ "description": "FEC instances of this track (draft-ramadan-moq-fec-catalog-00 3.1). An empty or absent array declares no FEC. The fec-01 single-object shape is not accepted.",
215
+ "items": { "$ref": "#/$defs/fecInstance" }
216
+ },
217
+ "fecInstance": {
229
218
  "type": "object",
230
- "additionalProperties": false,
231
- "required": ["algorithm", "sourceSymbols", "repairSymbols", "symbolSize", "repairTrack"],
219
+ "$comment": "No additionalProperties: false: unknown keys are ignored (draft-ramadan-moq-fec-catalog-00 3.4). This states what a publisher emits; a receiver binds through validateFecCatalog, which fails closed per instance.",
220
+ "required": ["scheme", "blockProfile", "sourceSymbols", "repairSymbols", "symbolSize", "repairTrack"],
232
221
  "properties": {
233
- "algorithm": {
222
+ "instanceId": { "type": "integer", "minimum": 0, "maximum": 255, "default": 0 },
223
+ "scheme": {
234
224
  "type": "string",
235
225
  "minLength": 1,
236
- "description": "FEC scheme token (draft-ramadan-moq-fec-01 4): raptorq, reed-solomon, none, or another registered value. A receiver that does not implement the token consumes the track without FEC; it is not a catalog error."
226
+ "description": "FEC scheme (draft-ramadan-moq-fec-block-codes-00): raptorq or reed-solomon. A receiver that does not implement it forgoes this instance. \"none\" is reserved and never means no FEC (draft-ramadan-moq-fec-core-00 15.2)."
237
227
  },
238
228
  "blockProfile": {
239
- "enum": ["iso-ssbg1-v1"],
240
- "description": "FEC block profile (ISO/IEC 23008-1 Annex C, draft-ramadan-moq-fec 5.1). Required whenever algorithm declares an FEC instance (anything but \"none\"); never defaulted or inferred. The only profile is iso-ssbg1-v1: ssbg_mode1, K' = SSB_length, consecutive SS_IDs. Any other token, the retired k-padded-v1 included, is invalid; a receiver treats such a track as having no FEC."
229
+ "type": "string",
230
+ "minLength": 1,
231
+ "description": "FEC block profile: moq-object-v1, or iso-ssbg1-v1 on an mmtp track. Required, never defaulted (draft-ramadan-moq-fec-catalog-00 3.1)."
241
232
  },
242
233
  "sourceSymbols": { "$ref": "#/$defs/positiveInteger" },
243
234
  "repairSymbols": { "$ref": "#/$defs/positiveInteger" },
@@ -245,28 +236,32 @@
245
236
  "interleaveDepthMs": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
246
237
  "reorderToleranceMs": { "$ref": "#/$defs/nonNegativeNumber" },
247
238
  "repairTrack": { "type": "string", "minLength": 1 },
248
- "repairContainer": { "const": "native" },
249
- "mode": { "enum": ["interleaved", "object"] }
239
+ "symbolTrack": { "type": "string", "minLength": 1 },
240
+ "label": { "type": "string", "description": "Human-readable only; never drives behaviour." },
241
+ "scope": {
242
+ "const": "keyframe",
243
+ "description": "Marks a keyframe overlay instance (draft-ramadan-moq-mmt-02 7.4.1)."
244
+ }
250
245
  },
251
246
  "allOf": [
252
247
  {
253
- "if": { "properties": { "algorithm": { "const": "none" } }, "required": ["algorithm"] },
254
- "else": { "properties": { "blockProfile": true }, "required": ["blockProfile"] }
255
- },
256
- {
257
- "$comment": "draft-ramadan-moq-fec-01 6.3.4 rule 13: under iso-ssbg1-v1 a symbol is at least 14 octets (Section 7.3); a shorter symbol carries no protected MMTP packet.",
248
+ "$comment": "draft-ramadan-moq-mmt-02 7.1: under iso-ssbg1-v1 a symbol is at least 14 octets.",
258
249
  "if": { "properties": { "blockProfile": { "const": "iso-ssbg1-v1" } }, "required": ["blockProfile"] },
259
250
  "then": { "properties": { "symbolSize": { "type": "integer", "minimum": 14 } } }
260
251
  },
261
252
  {
262
- "if": { "properties": { "algorithm": { "const": "raptorq" } }, "required": ["algorithm"] },
253
+ "if": { "properties": { "scheme": { "const": "raptorq" } }, "required": ["scheme"] },
263
254
  "then": {
264
255
  "properties": {
265
256
  "symbolSize": { "type": "integer", "minimum": 1, "multipleOf": 8 },
266
- "sourceSymbols": { "type": "integer", "maximum": 56403 },
267
- "repairSymbols": { "type": "integer", "maximum": 16777215 }
257
+ "sourceSymbols": { "type": "integer", "maximum": 56403 }
268
258
  }
269
259
  }
260
+ },
261
+ {
262
+ "$comment": "draft-ramadan-moq-fec-core-00 10.3 rule 11: interleaveDepthMs 0 only under iso-ssbg1-v1.",
263
+ "if": { "properties": { "interleaveDepthMs": { "const": 0 } }, "required": ["interleaveDepthMs"] },
264
+ "then": { "properties": { "blockProfile": { "const": "iso-ssbg1-v1" } }, "required": ["blockProfile"] }
270
265
  }
271
266
  ]
272
267
  },
@@ -11,23 +11,10 @@
11
11
  import {
12
12
  MMTP_PACKET_ID_MAX,
13
13
  MMTP_SIGNALING_PACKET_ID,
14
- ISO_SSBG1_MIN_SYMBOL_SIZE,
15
- isFecBlockProfile,
16
14
  type FecBlockProfile,
17
15
  } from "@blockcast/mmt-base";
18
16
  import { multicastCatalogIssues, multicastEndpointSource } from "@blockcast/mmt-base";
19
17
  import type { EndpointAuthConfig, MulticastAuthConfig } from "./multicast-auth-catalog.js";
20
- import {
21
- assembleRepairLayers,
22
- assertRepairTrackReachable,
23
- describeRepairTrack,
24
- rejectRepairFieldsOnMediaTrack,
25
- rejectRepairTrackGeometry,
26
- repairReachability,
27
- dependsTarget,
28
- readFecBlockProfile,
29
- validateRepairTrackFields,
30
- } from "./repair-layers.js";
31
18
 
32
19
  // ── Track Role (Phase 14, D-02..D-05) ─────────────────────────
33
20
 
@@ -164,125 +151,51 @@ export function validateSwitchingSet(
164
151
  return { ok: true };
165
152
  }
166
153
 
167
- // ── FEC Catalog Extension (CARP §3.2) ─────────────────────────
168
-
169
- /** draft-ramadan-moq-fec-01 4: `none` and the two specified schemes, or any
170
- * other registered token, which a receiver that does not implement it
171
- * consumes without FEC (`fecAlgorithmUnimplemented`). */
172
- export type FecAlgorithm = "none" | "raptorq" | "reed-solomon" | (string & {});
154
+ // ── FEC Catalog Extension (draft-ramadan-moq-fec-catalog-00 3) ─
173
155
 
174
- export interface FecCatalogConfig {
175
- /** FEC algorithm identifier (REQUIRED per §4.1, values per §3.1). */
176
- algorithm: FecAlgorithm;
177
- /** FEC block profile (BLO-35597, ISO/IEC 23008-1 Annex C). REQUIRED when
178
- * `algorithm` declares an FEC instance (anything but `"none"`); optional
179
- * on `"none"`, where a present token must still be implemented. The only
180
- * profile is `iso-ssbg1-v1` (ssbg_mode1, K' = SSB_length, consecutive
181
- * SS_IDs; draft-ramadan-moq-fec §5.1). Never defaulted and never inferred
182
- * from the wire: a track naming any other token (the retired `k-padded-v1`
183
- * included) has no FEC (`TrackFecUnavailable`). */
184
- blockProfile?: FecBlockProfile;
185
- /** K — source symbols per block (REQUIRED). */
156
+ /**
157
+ * One FEC instance object (draft-ramadan-moq-fec-catalog-00 3.1). A track's
158
+ * `fec` is an array of these ({@link FecCatalogConfig}); an empty array, or no
159
+ * `fec`, declares no FEC. Member names are case sensitive. This is the shape
160
+ * only: whether an instance is usable is `validateFecCatalog`'s answer (fail
161
+ * closed per instance), and runtime readers take it from `resolveFecBindings`,
162
+ * never from a local predicate.
163
+ */
164
+ export interface FecInstanceConfig {
165
+ /** 0..255, unique per track; absent means 0. */
166
+ instanceId?: number;
167
+ /** `raptorq`, `reed-solomon`, or any other registered token, which a
168
+ * receiver that does not implement it consumes without FEC. */
169
+ scheme: string;
170
+ /** REQUIRED, never defaulted. This library implements `iso-ssbg1-v1`
171
+ * (BLO-35597); any other token is no FEC on that instance. */
172
+ blockProfile: FecBlockProfile | (string & {});
173
+ /** K. */
186
174
  sourceSymbols: number;
187
- /** P — repair symbols per block (REQUIRED). */
175
+ /** P of layer 0, >= 1. */
188
176
  repairSymbols: number;
189
- /** T — bytes per symbol, 8-byte aligned (REQUIRED). */
177
+ /** T, bytes per symbol. */
190
178
  symbolSize: number;
191
- /** First-to-last source/repair send span in milliseconds.
192
- * Zero or absence means no elapsed deadline; in-band FEC remains usable.
193
- * BLO-14526: renamed from the legacy `interleaveDepth` count-era key — no fallback read. */
194
179
  interleaveDepthMs?: number;
195
- /** Declared jitter/reorder allowance in MILLISECONDS.
196
- * Added to interleaveDepthMs to form a block's deadline; never multiplied
197
- * by it. Jitter is a property of the network path and the block span is a
198
- * property of the encoder, so a multiplicative "safety factor" couples two
199
- * independent quantities and grants different slack per track for the same
200
- * network. Consumed identically by FECManager, FEC workers and player
201
- * policies so one signaled value governs end to end. */
180
+ /** Additive jitter/reorder allowance in milliseconds (never a multiplier). */
202
181
  reorderToleranceMs?: number;
203
- /** Track name for repair symbols (REQUIRED per §6.1). Base layer 0 when the
204
- * source carries further repair layers or a keyframe overlay: those are
205
- * self-describing `fec-repair` tracks (`depends`, `repairLayer`, `scope`)
206
- * per draft-ramadan-moq-fec §6.3, never listed here. */
182
+ /** Layer-0 repair track. Further layers and an overlay are `fec-repair`
183
+ * tracks naming this track in `source` and this instance's `instanceId`. */
207
184
  repairTrack: string;
208
- /** FEC block mode: "interleaved" (default) or "object" (per-frame). */
209
- // Blockcast extension, not in draft-ramadan-moq-fec-01 (its public object protection is the moq-object-v1 block profile, Section 7.6, which libmmt does not implement)
210
- mode?: "interleaved" | "object";
211
- /** UEP (Unequal Error Protection) — Blockcast extension, not in draft-ramadan-moq-fec-01. When present, repair count
212
- * varies per frame type. repairSymbols becomes the base (P-frame) count.
213
- * MUST be omitted on multi-track base/delta streams (D-08). */
214
- uep?: {
215
- /** Keyframe repair ratio: P = ceil(K * keyRatio). Default 0.5. */
216
- keyRatio: number;
217
- /** Delta frame repair ratio: P = ceil(K * deltaRatio). Default 0.25. */
218
- deltaRatio: number;
219
- };
220
- /**
221
- * Base-only fallback window in FEC blocks (D-11, Phase 14).
222
- *
223
- * When the delta track's FEC block failure rate exceeds the derived threshold
224
- * over this many consecutive blocks, MediaRouter falls back to base-only mode.
225
- * Derivation: framerate * fallback_window_seconds. If absent, construction
226
- * of the multi-track subscriber MUST throw (error on bad init).
227
- *
228
- * Present only on the base track's fec config (not the delta or repair track).
229
- */
185
+ symbolTrack?: string;
186
+ /** Display only; never drives behaviour. */
187
+ label?: string;
188
+ /** Phase-14 base-only fallback window in FEC blocks (D-11). Blockcast
189
+ * extension: an unrecognized instance key the validator ignores
190
+ * (catalog-00 3.4); read by `resolveDeltaFallback`. */
230
191
  deltaFailWindowBlocks?: number;
231
- /**
232
- * Delta re-subscribe probe interval in milliseconds (D-13, Phase 14).
233
- *
234
- * After a base-only fallback, wait this long before re-subscribing to the
235
- * delta track to check if conditions have improved.
236
- * Derivation: computeFecDeliveryWindowMs(...) — the single FEC recovery-window
237
- * source of truth (draft-ramadan-moq-fec §9). Never a fixed interleave multiple.
238
- *
239
- * Present only on the base track's fec config (not the delta or repair track).
240
- */
192
+ /** Phase-14 delta re-subscribe probe interval in ms (D-13). Blockcast
193
+ * extension, as above. */
241
194
  deltaProbeIntervalMs?: number;
242
- /**
243
- * Jitter + one-way-delay + reorder budget in MILLISECONDS (draft §9).
244
- *
245
- * An explicit ADDITIVE margin over the complete signaled FEC block span.
246
- * OPTIONAL: absence does not authorize a local default or a span multiplier.
247
- */
248
- jitterBufferMs?: number;
249
- }
250
-
251
- /**
252
- * Whether a catalog `fec` block carries the FEC instance geometry a receiver
253
- * configures, its block profile aside: a non-empty `algorithm` token, positive
254
- * `sourceSymbols`, `repairSymbols` and `symbolSize` (8-byte aligned for
255
- * RaptorQ), and a non-empty `repairTrack`. A block that fails this is
256
- * malformed whatever its profile. Total: never throws.
257
- */
258
- export function isFecCatalogGeometry(obj: unknown): boolean {
259
- if (typeof obj !== "object" || obj === null) return false;
260
- const o = obj as Record<string, unknown>;
261
- const algorithm = o["algorithm"];
262
- const symbolSize = o["symbolSize"];
263
- return (
264
- typeof algorithm === "string" && algorithm.length > 0 &&
265
- typeof o["sourceSymbols"] === "number" && o["sourceSymbols"] >= 1 &&
266
- typeof o["repairSymbols"] === "number" && o["repairSymbols"] >= 1 &&
267
- typeof symbolSize === "number" && symbolSize >= 1 &&
268
- (algorithm !== "raptorq" || symbolSize % 8 === 0) &&
269
- typeof o["repairTrack"] === "string" && (o["repairTrack"] as string).length > 0
270
- );
271
195
  }
272
196
 
273
- /**
274
- * Whether a catalog `fec` block is a complete `FecCatalogConfig`: its geometry
275
- * holds (`isFecCatalogGeometry`) and it names the implemented block profile,
276
- * `iso-ssbg1-v1` (BLO-35597). On `algorithm: "none"` the profile may be
277
- * omitted. Any other token, the retired `k-padded-v1` included, is not a
278
- * known profile, so this is false for it; a receiver resolves such a track to
279
- * no FEC (`TrackFecUnavailable`) rather than failing the session.
280
- */
281
- export function isFecCatalogConfig(obj: unknown): obj is FecCatalogConfig {
282
- if (!isFecCatalogGeometry(obj)) return false;
283
- const o = obj as Record<string, unknown>;
284
- return isFecBlockProfile(o["blockProfile"]) || (o["algorithm"] === "none" && o["blockProfile"] === undefined);
285
- }
197
+ /** A track's `fec` member (draft-ramadan-moq-fec-catalog-00 3). */
198
+ export type FecCatalogConfig = readonly FecInstanceConfig[];
286
199
 
287
200
  // ── Multicast Catalog Extension (CARP §4.1-4.4) ───────────────
288
201
 
@@ -866,7 +779,7 @@ export function validateCmsfMmtpCatalog(
866
779
  );
867
780
  }
868
781
  const codec = track["codec"];
869
- if (packaging !== "timeline" && packaging !== "mediatimeline" && packaging !== "eventtimeline" && packaging !== "fec-repair" &&
782
+ if (packaging !== "timeline" && packaging !== "mediatimeline" && packaging !== "eventtimeline" && packaging !== "fec-repair" && packaging !== "fec-source" &&
870
783
  (typeof codec !== "string" || codec.length === 0)) {
871
784
  catalogError("track_codec", `${path}.codec`, `codec is required for packaging=${packaging}`);
872
785
  }
@@ -896,14 +809,10 @@ export function validateCmsfMmtpCatalog(
896
809
  if (initRef !== undefined && (typeof initRef !== "string" || initRef.length === 0)) {
897
810
  catalogError("track_init_ref", `${path}.initRef`, "must be a non-empty string");
898
811
  }
899
- // draft-ramadan-moq-fec §6.3: repair tracks describe themselves and carry
900
- // no block geometry; media tracks never carry repair-track fields.
901
- if (packaging === "fec-repair") {
902
- rejectRepairTrackGeometry(path, track, catalogError);
903
- validateRepairTrackFields(describeRepairTrack(name, path, track), catalogError);
904
- } else {
905
- rejectRepairFieldsOnMediaTrack(path, track, catalogError);
906
- }
812
+ // FEC binding (draft-ramadan-moq-fec-catalog-00) is `validateFecCatalog`'s:
813
+ // it fails closed per instance and never stops another track, so nothing
814
+ // here throws on `fec` or on a repair or symbol track. A -01 single-object
815
+ // `fec` therefore costs only that track's FEC (BLO-50171).
907
816
  if (packaging === "mmtp") {
908
817
  // mmt-01 12.1: MUST be "mfu"; a subscriber rejects anything else.
909
818
  if (track["mmtpMode"] !== "mfu") {
@@ -924,105 +833,6 @@ export function validateCmsfMmtpCatalog(
924
833
  catalogError("track_group_duration_exact", `${path}.groupDurationMs`, "does not resolve to an exact integer tick count");
925
834
  }
926
835
  }
927
-
928
- if (track["fec"] !== undefined) {
929
- const fec = track["fec"];
930
- if (typeof fec !== "object" || fec === null || Array.isArray(fec)) {
931
- catalogError("track_fec", `${path}.fec`, "FEC must be an object");
932
- }
933
- const config = fec as Record<string, unknown>;
934
- const algorithm = config["algorithm"];
935
- // Any token is valid (fec-01 4): a receiver that does not implement it
936
- // plays the track without FEC (`fecAlgorithmUnimplemented`).
937
- if (typeof algorithm !== "string" || algorithm.length === 0) {
938
- catalogError("track_fec", `${path}.fec.algorithm`, "must be a non-empty string");
939
- }
940
- if ("interleaveDepth" in config || "interleaveMs" in config) {
941
- catalogError("track_fec", `${path}.fec`, "interleaveDepthMs is the only supported interleave field");
942
- }
943
- if ("enhancementRepair" in config) {
944
- catalogError(
945
- "track_fec",
946
- `${path}.fec.enhancementRepair`,
947
- "repair layers are self-describing fec-repair tracks (depends, repairLayer); fec.enhancementRepair is not supported",
948
- );
949
- }
950
- if (config["interleaveDepthMs"] !== undefined &&
951
- (!Number.isSafeInteger(config["interleaveDepthMs"]) || (config["interleaveDepthMs"] as number) < 0)) {
952
- catalogError("track_fec", `${path}.fec.interleaveDepthMs`, "must be a non-negative safe integer");
953
- }
954
- if (algorithm !== "none") {
955
- for (const field of ["sourceSymbols", "repairSymbols", "symbolSize"] as const) {
956
- if (!positiveInteger(config[field])) {
957
- catalogError("track_fec", `${path}.fec.${field}`, "must be a positive integer");
958
- }
959
- }
960
- // The receiver refuses unaligned RaptorQ geometry (`isFecCatalogGeometry`);
961
- // the schema's `multipleOf: 8` says the same, so the validator does too.
962
- if (algorithm === "raptorq" && (config["symbolSize"] as number) % 8 !== 0) {
963
- catalogError("track_fec", `${path}.fec.symbolSize`, "RaptorQ symbolSize must be a multiple of 8");
964
- }
965
- if (typeof config["repairTrack"] !== "string" || config["repairTrack"].length === 0) {
966
- catalogError("track_fec", `${path}.fec.repairTrack`, "must be a non-empty string");
967
- }
968
- }
969
- // Required on a declared FEC instance, optional but implemented on "none".
970
- readFecBlockProfile(config["blockProfile"], `${path}.fec.blockProfile`, algorithm !== "none", catalogError);
971
- // draft-ramadan-moq-fec-01 6.3.4 rule 13: an iso-ssbg1-v1 symbol shorter
972
- // than 14 octets carries no protected MMTP packet (7.3).
973
- if (algorithm !== "none" && (config["symbolSize"] as number) < ISO_SSBG1_MIN_SYMBOL_SIZE) {
974
- catalogError("track_fec", `${path}.fec.symbolSize`, "iso-ssbg1-v1 symbolSize must be at least 14");
975
- }
976
- }
977
- }
978
-
979
- // Repair tracks (draft-ramadan-moq-fec §6.3). Every fec-repair track is
980
- // described once; each FEC source then assembles its base layer chain and
981
- // overlays through the shared contract, which fails closed with a code and
982
- // the JSON path of the offending field.
983
- const repairDescriptors = trackRecords.flatMap((track, index) =>
984
- track["packaging"] === "fec-repair"
985
- ? [describeRepairTrack(track["name"] as string, `$.tracks[${index}]`, track)]
986
- : [],
987
- );
988
- // Rule 9 (no orphan repair track) reads the same derivation the resolver
989
- // uses; see `repairReachability` in repair-layers.ts for its scope.
990
- const reachability = repairReachability(
991
- trackRecords.filter((track) => track["packaging"] !== "fec-repair").map((track) => track["fec"]),
992
- );
993
- for (const descriptor of repairDescriptors) {
994
- assertRepairTrackReachable(descriptor, reachability, catalogError);
995
- const target = dependsTarget(descriptor);
996
- if (target === undefined) continue;
997
- const source = trackRecords.find((candidate) => candidate["name"] === target);
998
- const sourceFec = source?.["fec"] as Record<string, unknown> | undefined;
999
- if (!source || source["packaging"] !== "mmtp" || !sourceFec || sourceFec["algorithm"] === "none") {
1000
- catalogError(
1001
- "track_repair_depends",
1002
- `${descriptor.path}.depends`,
1003
- `${JSON.stringify(target)} is not an mmtp track with FEC`,
1004
- );
1005
- }
1006
- }
1007
- for (let index = 0; index < trackRecords.length; index++) {
1008
- const track = trackRecords[index]!;
1009
- const fec = track["fec"] as Record<string, unknown> | undefined;
1010
- if (!fec || fec["algorithm"] === "none") continue;
1011
- // A dangling fec.repairTrack (missing, or not a fec-repair track) fails
1012
- // inside the shared contract, so the resolver and this validator agree.
1013
- assembleRepairLayers(
1014
- {
1015
- name: track["name"] as string,
1016
- path: `$.tracks[${index}].fec`,
1017
- algorithm: fec["algorithm"],
1018
- sourceSymbols: fec["sourceSymbols"],
1019
- repairSymbols: fec["repairSymbols"],
1020
- repairTrack: fec["repairTrack"],
1021
- },
1022
- repairDescriptors,
1023
- catalogError,
1024
- { layerZeroMustBeDeclared: true },
1025
- );
1026
836
  }
1027
837
 
1028
838
  // mmt-01 §4.4: alternates share group boundaries so a subscriber can switch