@blockcast/mmt-container 0.4.1 → 0.5.0-main.2c1f81817995

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 (72) hide show
  1. package/README.md +2 -2
  2. package/dist/browser-runtime.js +3458 -356
  3. package/dist/browser-runtime.js.map +4 -4
  4. package/dist/cmcd-producer.d.ts +5 -1
  5. package/dist/cmcd-producer.d.ts.map +1 -1
  6. package/dist/cmcd-producer.js.map +2 -2
  7. package/dist/delivery-catalog.js +18 -2
  8. package/dist/delivery-catalog.js.map +2 -2
  9. package/dist/fec-manager.d.ts +21 -16
  10. package/dist/fec-manager.d.ts.map +1 -1
  11. package/dist/fec-manager.js +177 -80
  12. package/dist/fec-manager.js.map +3 -3
  13. package/dist/held-layer-admission.d.ts +58 -0
  14. package/dist/held-layer-admission.d.ts.map +1 -0
  15. package/dist/held-layer-admission.js +105 -0
  16. package/dist/held-layer-admission.js.map +7 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js.map +2 -2
  19. package/dist/mmtp-container-parser.no-modules.js +2682 -1023
  20. package/dist/mmtp-container-parser.no-modules.js.map +4 -4
  21. package/dist/mmtp-fec-session.d.ts +36 -3
  22. package/dist/mmtp-fec-session.d.ts.map +1 -1
  23. package/dist/mmtp-fec-session.js +114 -5
  24. package/dist/mmtp-fec-session.js.map +3 -3
  25. package/dist/mmtp-router.d.ts +72 -28
  26. package/dist/mmtp-router.d.ts.map +1 -1
  27. package/dist/mmtp-router.js +321 -203
  28. package/dist/mmtp-router.js.map +2 -2
  29. package/dist/multicast-auth-catalog.d.ts +10 -2
  30. package/dist/multicast-auth-catalog.d.ts.map +1 -1
  31. package/dist/multicast-auth-catalog.js +422 -22
  32. package/dist/multicast-auth-catalog.js.map +4 -4
  33. package/dist/multicast-manager.d.ts +48 -9
  34. package/dist/multicast-manager.d.ts.map +1 -1
  35. package/dist/multicast-manager.js +3406 -340
  36. package/dist/multicast-manager.js.map +4 -4
  37. package/dist/multicast-transport.d.ts +4 -1
  38. package/dist/multicast-transport.d.ts.map +1 -1
  39. package/dist/multicast-transport.js +20 -3
  40. package/dist/multicast-transport.js.map +2 -2
  41. package/dist/no-modules.d.ts +1 -2
  42. package/dist/no-modules.d.ts.map +1 -1
  43. package/dist/no-modules.js +2668 -1007
  44. package/dist/no-modules.js.map +4 -4
  45. package/dist/repair-fetch.d.ts +10 -1
  46. package/dist/repair-fetch.d.ts.map +1 -1
  47. package/dist/repair-fetch.js +5 -2
  48. package/dist/repair-fetch.js.map +2 -2
  49. package/package.json +5 -14
  50. package/src/cmcd-producer.ts +6 -1
  51. package/src/fec-manager.ts +121 -131
  52. package/src/held-layer-admission.ts +161 -0
  53. package/src/index.ts +5 -9
  54. package/src/mmtp-fec-session.ts +76 -3
  55. package/src/mmtp-router.ts +390 -272
  56. package/src/multicast-auth-catalog.ts +81 -8
  57. package/src/multicast-manager.ts +189 -48
  58. package/src/multicast-transport.ts +6 -2
  59. package/src/no-modules.ts +1 -6
  60. package/src/repair-fetch.ts +24 -4
  61. package/dist/manifest-auth-plan.d.ts +0 -31
  62. package/dist/manifest-auth-plan.d.ts.map +0 -1
  63. package/dist/manifest-auth-plan.js +0 -426
  64. package/dist/manifest-auth-plan.js.map +0 -7
  65. package/dist/source-auth.d.ts +0 -2
  66. package/dist/source-auth.d.ts.map +0 -1
  67. package/dist/source-auth.js +0 -3912
  68. package/dist/source-auth.js.map +0 -7
  69. package/src/manifest-auth-plan.ts +0 -55
  70. package/src/source-auth.ts +0 -1
  71. package/test-vectors/generate-source-auth-fixture.mjs +0 -114
  72. package/test-vectors/source-auth-v1.json +0 -61
package/src/index.ts CHANGED
@@ -39,15 +39,11 @@ export {
39
39
  export { isCompleteFmp4DataUnit } from './fmp4-completion.js'
40
40
 
41
41
 
42
- // source-auth (authenticated-manifest / Merkle) is intentionally NOT re-exported
43
- // from the eager index barrel. It statically imports blake3 from @noble/hashes,
44
- // and re-exporting it here pulls that bare specifier into every browser consumer
45
- // of @blockcast/mmt-container (the moq-lib / shred / moqtail players load the
46
- // barrel as raw ESM via an import map, where @noble/hashes has no mapping →
47
- // "Failed to resolve module specifier". No browser player uses source-auth.)
48
- // Consumers that need it import the dedicated subpath: '@blockcast/mmt-container/source-auth'.
49
- // The same rule covers the @blockcast/mmt-manifest-verify surface (ManifestVerifier et al.):
50
- // import it from that package (or the subpath above) directly, never from this barrel.
42
+ // The bc-provenance v2 surface (@blockcast/mmt-manifest-verify) is intentionally
43
+ // NOT re-exported from the eager index barrel: it imports @noble/curves and
44
+ // @noble/hashes, bare specifiers the moq-lib / shred / moqtail players (which
45
+ // load the barrel as raw ESM via an import map) cannot resolve. Import it from
46
+ // that package directly. The router's provenance hooks take it by type only.
51
47
 
52
48
  // H.264/AVC + H.265/HEVC Codec Utilities
53
49
  export {
@@ -18,6 +18,11 @@
18
18
  * The retention is derived from the catalog's `fecBlockBudgetMs`, and a
19
19
  * catalog missing either input throws rather than defaulting.
20
20
  *
21
+ * Every repair packet runs the fec-01 6.3.9 held-layer check (layer 0, B_0 =
22
+ * catalog `fec.repairSymbols`) through the same HeldLayerAdmission FECManager
23
+ * uses. A packet below B_0 at or after the anchor fails the session closed:
24
+ * `stats.failClosed` reports why, and every later repair is refused.
25
+ *
21
26
  * Every repair packet that cannot be used is counted under the reason it was
22
27
  * refused, and every block ends under exactly one outcome, so a stream whose
23
28
  * FEC is doing nothing can be told apart from one with nothing to repair.
@@ -37,6 +42,14 @@ import {
37
42
  type FecBlockProfile,
38
43
  } from '@blockcast/mmt-base'
39
44
  import { deriveFecRetentionBlocks, fecBlockBudgetMs } from '@blockcast/mmt-fec'
45
+ import {
46
+ HeldLayerAdmission,
47
+ heldLayerAnchorHorizon,
48
+ type FecFailClosedReason,
49
+ type HeldLayerInput,
50
+ } from './held-layer-admission.js'
51
+
52
+ export type { FecFailClosedReason, HeldLayerInput } from './held-layer-admission.js'
40
53
 
41
54
  /** The mmt-wasm `MmtFecDecoder` members the session drives. */
42
55
  export interface MmtpFecSessionDecoder {
@@ -67,6 +80,10 @@ export interface MmtpFecSessionCatalogFec {
67
80
  algorithm?: unknown
68
81
  blockProfile?: unknown
69
82
  sourceSymbols?: unknown
83
+ /** P_0: layer 0's repair symbols per block, the fec-01 6.3.9 total B_0. */
84
+ repairSymbols?: unknown
85
+ /** Layer 0's repair track; names it for addRepair()'s `repairTrackName`. */
86
+ repairTrack?: unknown
70
87
  symbolSize?: unknown
71
88
  interleaveDepthMs?: unknown
72
89
  reorderToleranceMs?: unknown
@@ -77,6 +94,14 @@ export interface MmtpFecSessionConfig {
77
94
  fec: MmtpFecSessionCatalogFec
78
95
  /** Builds the RaptorQ decoder, normally `wasm_bindgen.MmtFecDecoder`. */
79
96
  Decoder: MmtpFecSessionDecoderConstructor
97
+ /**
98
+ * The source's repair layers as the catalog resolves them
99
+ * (`TrackFecParams.repairLayers`), for a caller that subscribes more than
100
+ * layer 0. Absent: layer 0 alone, B_0 = `fec.repairSymbols`.
101
+ */
102
+ repairLayers?: readonly HeldLayerInput[]
103
+ /** The layers the caller holds (fec-01 6.3.9). Absent: [0]. */
104
+ heldLayers?: readonly number[]
80
105
  }
81
106
 
82
107
  /**
@@ -139,6 +164,16 @@ export interface MmtpFecSessionStats {
139
164
  * contributed nothing to its block and do not make it mixed.
140
165
  */
141
166
  mixedPath: MmtpFecSessionMixedPathStats
167
+ /**
168
+ * fec-01 6.3.9: null until a repair packet at or after the anchor (the
169
+ * earliest block, in SS_Start serial order, whose repair carried
170
+ * RSB_length >= B_0) states an RSB_length below B_0; then why. Terminal:
171
+ * every later repair is refused under `repair:fail-closed` (the packet that
172
+ * failed it is counted there too), and block state stays as it was. Sources
173
+ * are still taken and can still complete a block from repair admitted
174
+ * before the failure.
175
+ */
176
+ failClosed: FecFailClosedReason | null
142
177
  }
143
178
 
144
179
  interface BlockState {
@@ -160,6 +195,8 @@ export class MmtpFecSession {
160
195
  readonly blockProfile: FecBlockProfile
161
196
  readonly algorithm: 'raptorq' | 'reed-solomon'
162
197
  readonly sourceSymbols: number
198
+ /** P_0 = B_0, the held total every repair packet is checked against. */
199
+ readonly repairSymbols: number
163
200
  readonly symbolSize: number
164
201
  /** The decoder's retention, in blocks, derived from the catalog budget. */
165
202
  readonly retentionBlocks: number
@@ -178,6 +215,9 @@ export class MmtpFecSession {
178
215
  /** The newest SS_ID when the remembered SS_IDs were last pruned. */
179
216
  #prunedAt: number | undefined
180
217
  #repairPacketId: number | undefined
218
+ #newestSource: number | undefined
219
+ #heldLayers: HeldLayerAdmission
220
+ #anchorHorizon: number
181
221
  #stats: MmtpFecSessionStats = {
182
222
  sourceSymbols: 0,
183
223
  repairSymbols: 0,
@@ -191,11 +231,12 @@ export class MmtpFecSession {
191
231
  sourceSymbolsByPath: { moq: 0, multicast: 0 },
192
232
  repairSymbolsByPath: { moq: 0, multicast: 0 },
193
233
  mixedPath: { clean: 0, recovered: 0, failed: 0, released: 0 },
234
+ failClosed: null,
194
235
  }
195
236
 
196
237
  /**
197
238
  * @throws when the catalog block lacks or misstates a field the session
198
- * needs: the block profile, K, T, `interleaveDepthMs` or
239
+ * needs: the block profile, K, P_0 (`repairSymbols`), T, `interleaveDepthMs` or
199
240
  * `reorderToleranceMs`. None is defaulted.
200
241
  */
201
242
  constructor(config: MmtpFecSessionConfig) {
@@ -209,6 +250,7 @@ export class MmtpFecSession {
209
250
  }
210
251
  this.algorithm = fec.algorithm === 'reed-solomon' ? 'reed-solomon' : 'raptorq'
211
252
  this.sourceSymbols = requirePositiveInteger(fec.sourceSymbols, 'fec.sourceSymbols')
253
+ this.repairSymbols = requirePositiveInteger(fec.repairSymbols, 'fec.repairSymbols')
212
254
  this.symbolSize = requirePositiveInteger(fec.symbolSize, 'fec.symbolSize')
213
255
  const interleaveDepthMs = requireNumber(fec.interleaveDepthMs, 'fec.interleaveDepthMs')
214
256
  const reorderToleranceMs = requireNumber(fec.reorderToleranceMs, 'fec.reorderToleranceMs')
@@ -216,6 +258,18 @@ export class MmtpFecSession {
216
258
  fecBlockBudgetMs(interleaveDepthMs, reorderToleranceMs),
217
259
  interleaveDepthMs,
218
260
  )
261
+ this.#heldLayers = new HeldLayerAdmission(
262
+ config.repairLayers ?? [
263
+ {
264
+ layer: 0,
265
+ repairTrackName: typeof fec.repairTrack === 'string' ? fec.repairTrack : '',
266
+ rsIdOffset: 0,
267
+ repairSymbols: this.repairSymbols,
268
+ },
269
+ ],
270
+ config.heldLayers,
271
+ )
272
+ this.#anchorHorizon = heldLayerAnchorHorizon(this.retentionBlocks, this.sourceSymbols)
219
273
  if (typeof config.Decoder !== 'function') {
220
274
  throw new Error('MmtpFecSession: a decoder constructor (wasm_bindgen.MmtFecDecoder) is required')
221
275
  }
@@ -241,6 +295,7 @@ export class MmtpFecSession {
241
295
  sourceSymbolsByPath: { ...this.#stats.sourceSymbolsByPath },
242
296
  repairSymbolsByPath: { ...this.#stats.repairSymbolsByPath },
243
297
  mixedPath: { ...this.#stats.mixedPath },
298
+ failClosed: this.#stats.failClosed && { ...this.#stats.failClosed },
244
299
  }
245
300
  }
246
301
 
@@ -266,6 +321,7 @@ export class MmtpFecSession {
266
321
  this.#stats.sourceSymbols++
267
322
  this.#stats.sourceSymbolsByPath[path]++
268
323
  this.#noteNewest(ssId)
324
+ if (this.#newestSource === undefined || serial32Precedes(this.#newestSource, ssId)) this.#newestSource = ssId
269
325
 
270
326
  const start = this.#blockStartOf(ssId)
271
327
  const block = start === undefined ? undefined : this.#blocks.get(start)
@@ -298,14 +354,17 @@ export class MmtpFecSession {
298
354
  * Feeds one object of the repair track: a whole MMTP repair packet (ISO/IEC
299
355
  * 23008-1 C.5.3). A repair track carries one repair flow, so the first
300
356
  * repair packet fixes its packet ID and a packet of any other is refused.
301
- * `path` is where it was received, as for addSource().
357
+ * `path` is where it was received, as for addSource(). `repairTrackName`
358
+ * is the repair track it was received on, for the 6.3.9 own-track rule; a
359
+ * track the layers do not list is refused as `repair:unlisted-track`.
302
360
  *
303
361
  * @returns the protected packets this repair symbol recovered, as for
304
362
  * addSource().
305
363
  */
306
- addRepair(packet: Uint8Array, path: MmtpFecSessionPath = 'moq'): Uint8Array[] {
364
+ addRepair(packet: Uint8Array, path: MmtpFecSessionPath = 'moq', repairTrackName?: string): Uint8Array[] {
307
365
  const decoder = this.#decoder
308
366
  if (!decoder) return this.#drop('repair:released')
367
+ if (this.#stats.failClosed) return this.#drop('repair:fail-closed')
309
368
  const pathBit = PATH_BITS[path]
310
369
  if (!pathBit) return this.#drop('repair:unknown-path')
311
370
  let header
@@ -328,6 +387,20 @@ export class MmtpFecSession {
328
387
  if (this.algorithm === 'reed-solomon' && frame.ssbLength + frame.rsbLength > 255) {
329
388
  return this.#drop('repair:rs-block-length')
330
389
  }
390
+ // fec-01 6.3.9, after the 4.5 discards and the size check, before a block
391
+ // is placed. Without a track name only the anchored rule (b) applies.
392
+ const verdict = this.#heldLayers.admit(
393
+ repairTrackName,
394
+ frame.ssStart,
395
+ frame.rsbLength,
396
+ this.#anchorHorizon,
397
+ this.#newestSource,
398
+ )
399
+ if (verdict === 'discard') return this.#drop('repair:unlisted-track')
400
+ if (verdict !== undefined) {
401
+ this.#stats.failClosed = verdict
402
+ return this.#drop('repair:fail-closed')
403
+ }
331
404
 
332
405
  const start = frame.ssStart
333
406
  let block = this.#blocks.get(start)