@blockcast/mmt-container 2.1.0 → 2.2.0-main.10bec7edb00b

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 (74) hide show
  1. package/dist/browser-runtime.js +647 -83
  2. package/dist/browser-runtime.js.map +4 -4
  3. package/dist/carp-catalog.js.map +1 -1
  4. package/dist/catalog-fec-params.d.ts +16 -0
  5. package/dist/catalog-fec-params.d.ts.map +1 -1
  6. package/dist/catalog-fec-params.js +15 -0
  7. package/dist/catalog-fec-params.js.map +2 -2
  8. package/dist/catalog-network-source.js.map +1 -1
  9. package/dist/cmcd-producer.d.ts +5 -0
  10. package/dist/cmcd-producer.d.ts.map +1 -1
  11. package/dist/cmcd-producer.js.map +2 -2
  12. package/dist/delivery-catalog.js +11 -6
  13. package/dist/delivery-catalog.js.map +2 -2
  14. package/dist/delta-fallback-config.js.map +1 -1
  15. package/dist/fec-manager.js.map +2 -2
  16. package/dist/fec-state.js.map +1 -1
  17. package/dist/fec-worker-setup.js.map +1 -1
  18. package/dist/held-layer-admission.js.map +1 -1
  19. package/dist/index.d.ts +3 -2
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +6 -0
  22. package/dist/index.js.map +2 -2
  23. package/dist/iso-block-locator.js.map +1 -1
  24. package/dist/mfu-reassembler.js +12 -19
  25. package/dist/mfu-reassembler.js.map +2 -2
  26. package/dist/mmtp-container-parser.js +12 -19
  27. package/dist/mmtp-container-parser.js.map +2 -2
  28. package/dist/mmtp-container-parser.no-modules.js +133 -34
  29. package/dist/mmtp-container-parser.no-modules.js.map +3 -3
  30. package/dist/mmtp-decode-pipeline.js +12 -19
  31. package/dist/mmtp-decode-pipeline.js.map +2 -2
  32. package/dist/mmtp-fec-session.d.ts +41 -3
  33. package/dist/mmtp-fec-session.d.ts.map +1 -1
  34. package/dist/mmtp-fec-session.js +118 -13
  35. package/dist/mmtp-fec-session.js.map +3 -3
  36. package/dist/mmtp-router.d.ts +9 -1
  37. package/dist/mmtp-router.d.ts.map +1 -1
  38. package/dist/mmtp-router.js +1473 -33
  39. package/dist/mmtp-router.js.map +4 -4
  40. package/dist/multicast-auth-catalog.js.map +1 -1
  41. package/dist/multicast-input.js.map +1 -1
  42. package/dist/multicast-manager.d.ts +15 -2
  43. package/dist/multicast-manager.d.ts.map +1 -1
  44. package/dist/multicast-manager.js +15216 -14934
  45. package/dist/multicast-manager.js.map +4 -4
  46. package/dist/multicast-transport.d.ts +11 -3
  47. package/dist/multicast-transport.d.ts.map +1 -1
  48. package/dist/multicast-transport.js +266 -25
  49. package/dist/multicast-transport.js.map +4 -4
  50. package/dist/multicast-unwrap.js +12 -19
  51. package/dist/multicast-unwrap.js.map +2 -2
  52. package/dist/no-modules.js +130 -33
  53. package/dist/no-modules.js.map +3 -3
  54. package/dist/repair-fetch.js.map +1 -1
  55. package/dist/repair-layer-subscriptions.d.ts +82 -0
  56. package/dist/repair-layer-subscriptions.d.ts.map +1 -0
  57. package/dist/repair-layer-subscriptions.js +192 -0
  58. package/dist/repair-layer-subscriptions.js.map +7 -0
  59. package/dist/signaling.js.map +2 -2
  60. package/dist/timestamp-codec.js +12 -19
  61. package/dist/timestamp-codec.js.map +2 -2
  62. package/dist/wasm-mfu-reassembler.js.map +1 -1
  63. package/dist/wasm-mmtp-container-parser.js +12 -19
  64. package/dist/wasm-mmtp-container-parser.js.map +2 -2
  65. package/package.json +5 -5
  66. package/schemas/catalog.schema.json +4 -0
  67. package/src/catalog-fec-params.ts +35 -0
  68. package/src/cmcd-producer.ts +5 -0
  69. package/src/index.ts +12 -0
  70. package/src/mmtp-fec-session.ts +115 -15
  71. package/src/mmtp-router.ts +81 -16
  72. package/src/multicast-manager.ts +26 -5
  73. package/src/multicast-transport.ts +44 -20
  74. package/src/repair-layer-subscriptions.ts +261 -0
@@ -10,6 +10,8 @@ var PACKET_TYPE_MPU = 0;
10
10
  var PACKET_TYPE_REPAIR = 3;
11
11
  var MMTP_SIGNALING_PACKET_ID = 0;
12
12
  var MMTP_PACKET_ID_MAX = 65535;
13
+ var FEC_CODE_REED_SOLOMON = 1;
14
+ var FEC_CODE_RAPTORQ = 3;
13
15
  function parseMMTPHeader(buffer) {
14
16
  if (buffer.length < MMTP_HEADER_SIZE) {
15
17
  throw new Error(`MMTP packet too small: ${buffer.length} bytes`);
@@ -150,30 +152,26 @@ function isEmptyOrExactLegacyTrailer(payload, start, end) {
150
152
  const authLength = view2.getUint16(2, false);
151
153
  return start + LEGACY_TRAILER_HEADER_SIZE + authLength === end;
152
154
  }
153
- function resolveDeclaredTrailers(payload, declaredEnd, fecType, options, rapFlag = false) {
155
+ function resolveDeclaredTrailers(payload, declaredEnd, fecType, options) {
154
156
  if (declaredEnd > payload.length)
155
157
  return null;
156
158
  if (options?.skipFecId) {
157
- return isEmptyOrExactLegacyTrailer(payload, declaredEnd, payload.length) ? { hasFecId: false, hasOverlayFecId: false } : null;
159
+ return isEmptyOrExactLegacyTrailer(payload, declaredEnd, payload.length) ? { hasFecId: false } : null;
158
160
  }
159
- const overlay = options?.overlayDeclared === true && rapFlag;
160
161
  if (fecType === 1) {
161
- const fecStart = payload.length - sourceFecTrailerSize(overlay);
162
+ const fecStart = payload.length - SOURCE_FEC_PAYLOAD_ID_SIZE;
162
163
  if (fecStart < declaredEnd || !isEmptyOrExactLegacyTrailer(payload, declaredEnd, fecStart)) {
163
164
  return null;
164
165
  }
165
- return { hasFecId: true, hasOverlayFecId: overlay };
166
+ return { hasFecId: true };
166
167
  }
167
- if (fecType !== 0 || overlay)
168
+ if (fecType !== 0)
168
169
  return null;
169
170
  if (isEmptyOrExactLegacyTrailer(payload, declaredEnd, payload.length)) {
170
- return { hasFecId: false, hasOverlayFecId: false };
171
+ return { hasFecId: false };
171
172
  }
172
173
  return null;
173
174
  }
174
- function sourceFecTrailerSize(overlay) {
175
- return overlay ? 2 * SOURCE_FEC_PAYLOAD_ID_SIZE : SOURCE_FEC_PAYLOAD_ID_SIZE;
176
- }
177
175
  function planMfuExtraction(mmtpPacket, options, parsedHeader) {
178
176
  if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE) {
179
177
  return {
@@ -226,11 +224,11 @@ function planMfuExtraction(mmtpPacket, options, parsedHeader) {
226
224
  if ("rejection" in layoutResult)
227
225
  return layoutResult;
228
226
  const { layout } = layoutResult;
229
- const trailerLayout = resolveDeclaredTrailers(payload, layout.dataEnd, header.fecType, options, header.rapFlag);
227
+ const trailerLayout = resolveDeclaredTrailers(payload, layout.dataEnd, header.fecType, options);
230
228
  if (!trailerLayout) {
231
229
  if (!options?.skipFecId && header.fecType === 1) {
232
230
  const observed = payload.length - layout.dataEnd;
233
- const expected = sourceFecTrailerSize(options?.overlayDeclared === true && header.rapFlag);
231
+ const expected = SOURCE_FEC_PAYLOAD_ID_SIZE;
234
232
  if (observed !== expected) {
235
233
  return {
236
234
  rejection: {
@@ -262,13 +260,10 @@ function extractPlannedMfu(mmtpPacket, result) {
262
260
  const mfuDuHeader = mfuDuHeaderPresent && mpu.timedFlag === 1 ? parseTimedMfuDuHeader(payload, MPU_HEADER_SIZE) : null;
263
261
  const itemId = mfuDuHeaderPresent && mpu.timedFlag === 0 ? new DataView(payload.buffer, payload.byteOffset + MPU_HEADER_SIZE, 4).getUint32(0, false) : null;
264
262
  const data = payload.subarray(mfuOffset, dataEnd);
265
- const trailerStart = mmtpPacket.length - sourceFecTrailerSize(trailerLayout.hasOverlayFecId);
266
- const ssIdAt = (offset) => ({
267
- ssId: new DataView(mmtpPacket.buffer, mmtpPacket.byteOffset + offset, 4).getUint32(0, false)
268
- });
269
- const fecPayloadId = trailerLayout.hasFecId ? ssIdAt(trailerStart) : null;
270
- const overlayFecPayloadId = trailerLayout.hasOverlayFecId ? ssIdAt(trailerStart + SOURCE_FEC_PAYLOAD_ID_SIZE) : null;
271
- return { header, mpu, fragmentType, data, mfuDuHeader, itemId, fecPayloadId, overlayFecPayloadId };
263
+ const fecPayloadId = trailerLayout.hasFecId ? {
264
+ ssId: new DataView(mmtpPacket.buffer, mmtpPacket.byteOffset + mmtpPacket.length - SOURCE_FEC_PAYLOAD_ID_SIZE, 4).getUint32(0, false)
265
+ } : null;
266
+ return { header, mpu, fragmentType, data, mfuDuHeader, itemId, fecPayloadId };
272
267
  }
273
268
  function sourceFecPayloadIdOf(mmtpPacket, header) {
274
269
  if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE)
@@ -360,6 +355,50 @@ var NTP_SHORT_BITS = 32;
360
355
  var NTP_SHORT_MODULUS = 2 ** NTP_SHORT_BITS;
361
356
  var NTP_SHORT_HALF_RANGE = 2 ** (NTP_SHORT_BITS - 1);
362
357
 
358
+ // ../mmt-base/dist/signalling.js
359
+ var FEC_CODING_STRUCTURE_ONE_STAGE = 1;
360
+ var SSBG_MODE_1 = 1;
361
+ var RAPTORQ_MAX_SOURCE_SYMBOLS = 56403;
362
+ var REPAIR_SYMBOL_ID_MAX = 16777215;
363
+ var MIN_REPAIR_SYMBOL_SIZE = 14;
364
+ var REED_SOLOMON_MAX_BLOCK = 255;
365
+ function alFecRaptorQFlowConfig(flow) {
366
+ if (flow.fecCodingStructure !== FEC_CODING_STRUCTURE_ONE_STAGE || flow.repairFlows.length !== 1)
367
+ return null;
368
+ if (flow.ssbgMode !== SSBG_MODE_1 || flow.ffsrptsFlag !== 0 || flow.fecPayloadIdMode !== 0)
369
+ return null;
370
+ if (flow.packetIds.length !== 1)
371
+ return null;
372
+ const repair = flow.repairFlows[0];
373
+ const { fecCodeId, maximumK: k, maximumP: p } = repair;
374
+ const t = flow.repairSymbolSize;
375
+ if (t < MIN_REPAIR_SYMBOL_SIZE || k < 1 || p < 1 || k + p - 1 > REPAIR_SYMBOL_ID_MAX)
376
+ return null;
377
+ if (fecCodeId === FEC_CODE_RAPTORQ) {
378
+ if (t % 8 !== 0 || k > RAPTORQ_MAX_SOURCE_SYMBOLS)
379
+ return null;
380
+ } else if (fecCodeId === FEC_CODE_REED_SOLOMON) {
381
+ if (k + p > REED_SOLOMON_MAX_BLOCK)
382
+ return null;
383
+ } else {
384
+ return null;
385
+ }
386
+ if (repair.repairFlowId === MMTP_SIGNALING_PACKET_ID || repair.repairFlowId === flow.packetIds[0])
387
+ return null;
388
+ return {
389
+ sourcePacketIds: [...flow.packetIds],
390
+ repairFlowId: repair.repairFlowId,
391
+ fecCodeId,
392
+ ssbgMode: flow.ssbgMode,
393
+ fecPayloadIdMode: flow.fecPayloadIdMode,
394
+ repairSymbolSize: t,
395
+ maximumK: k,
396
+ maximumP: p,
397
+ protectionWindowTime: repair.protectionWindowTime,
398
+ protectionWindowSize: repair.protectionWindowSize
399
+ };
400
+ }
401
+
363
402
  // ../mmt-base/dist/serial-number.js
364
403
  var SERIAL32_SPAN = 4294967296;
365
404
  var SERIAL32_HALF = 2147483648;
@@ -8071,7 +8110,14 @@ var HeldLayerAdmission = class {
8071
8110
  var PATH_BITS = { moq: 1, multicast: 2 };
8072
8111
  var MIXED_PATHS = PATH_BITS.moq | PATH_BITS.multicast;
8073
8112
  var MmtpFecSession = class {
8113
+ /** P_0 = B_0, the held total every repair packet is checked against. */
8114
+ get repairSymbols() {
8115
+ return this.#repairSymbols;
8116
+ }
8074
8117
  #decoder;
8118
+ #repairSymbols;
8119
+ /** The message entry a catalog-less session was built from; undefined under a catalog. */
8120
+ #message;
8075
8121
  /** Blocks a repair has defined and that are not settled yet, by SS_Start. */
8076
8122
  #blocks = /* @__PURE__ */ new Map();
8077
8123
  /** SS_Starts of settled blocks, so a later repair for one is refused. */
@@ -8109,6 +8155,24 @@ var MmtpFecSession = class {
8109
8155
  * `interleaveDepthMs` or `reorderToleranceMs`. None is defaulted.
8110
8156
  */
8111
8157
  constructor(config) {
8158
+ if (config && "alFecFlow" in config) {
8159
+ const message = alFecRaptorQFlowConfig(config.alFecFlow);
8160
+ if (!message) {
8161
+ throw new Error("MmtpFecSession: the AL-FEC entry is outside draft-ramadan-moq-mmt-02 8.3 (iso-ssbg1-v1)");
8162
+ }
8163
+ this.#message = message;
8164
+ this.blockProfile = parseFecBlockProfile("iso-ssbg1-v1", "alFecFlow");
8165
+ this.algorithm = message.fecCodeId === FEC_CODE_RAPTORQ ? "raptorq" : "reed-solomon";
8166
+ this.sourceSymbols = message.maximumK;
8167
+ this.#repairSymbols = message.maximumP;
8168
+ this.symbolSize = message.repairSymbolSize;
8169
+ this.retentionBlocks = requirePositiveInteger(config.retentionBlocks, "retentionBlocks");
8170
+ this.#repairPacketId = message.repairFlowId;
8171
+ this.#heldLayers = layerZero(message.maximumP);
8172
+ this.#anchorHorizon = heldLayerAnchorHorizon(this.retentionBlocks, this.sourceSymbols);
8173
+ this.#decoder = buildDecoder(config.Decoder, this);
8174
+ return;
8175
+ }
8112
8176
  const fec = config?.fec;
8113
8177
  if (!fec || typeof fec !== "object") {
8114
8178
  throw new Error("MmtpFecSession: the catalog fec instance is required");
@@ -8119,7 +8183,7 @@ var MmtpFecSession = class {
8119
8183
  }
8120
8184
  this.algorithm = fec.scheme;
8121
8185
  this.sourceSymbols = requirePositiveInteger(fec.sourceSymbols, "fec.sourceSymbols");
8122
- this.repairSymbols = requirePositiveInteger(fec.repairSymbols, "fec.repairSymbols");
8186
+ this.#repairSymbols = requirePositiveInteger(fec.repairSymbols, "fec.repairSymbols");
8123
8187
  this.symbolSize = requirePositiveInteger(fec.symbolSize, "fec.symbolSize");
8124
8188
  const interleaveDepthMs = requireNumber(fec.interleaveDepthMs, "fec.interleaveDepthMs");
8125
8189
  const reorderToleranceMs = requireNumber(fec.reorderToleranceMs, "fec.reorderToleranceMs");
@@ -8139,17 +8203,30 @@ var MmtpFecSession = class {
8139
8203
  config.heldLayers
8140
8204
  );
8141
8205
  this.#anchorHorizon = heldLayerAnchorHorizon(this.retentionBlocks, this.sourceSymbols);
8142
- if (typeof config.Decoder !== "function") {
8143
- throw new Error("MmtpFecSession: a decoder constructor (wasm_bindgen.MmtFecDecoder) is required");
8144
- }
8145
- const decoder = new config.Decoder(this.retentionBlocks, this.blockProfile, this.algorithm);
8146
- try {
8147
- decoder.configure_params(this.sourceSymbols, this.symbolSize);
8148
- } catch (error) {
8149
- decoder.free();
8150
- throw error;
8151
- }
8152
- this.#decoder = decoder;
8206
+ this.#decoder = buildDecoder(config.Decoder, this);
8207
+ }
8208
+ /**
8209
+ * A later version of the AL-FEC message, catalog-less sessions only. A
8210
+ * maximum_p_for_repair_flow above the held total raises it (`raised`): the
8211
+ * anchored check restarts at the new total, so a block generated under the
8212
+ * previous one stays valid before the first block carrying the new total
8213
+ * (fec-core-00 7.3.2, mmt-02 8.3). A field at or below it changes nothing
8214
+ * (`unchanged`): no publisher lowers it, and the check catches a wire that
8215
+ * does. An entry with another asset, repair flow, code point, K or T, or
8216
+ * one outside 8.3, is `refused`: that is a new source encoding, which needs
8217
+ * a new session. A latched fail-closed stays latched either way.
8218
+ */
8219
+ updateAlFecFlow(flow) {
8220
+ const held = this.#message;
8221
+ if (!held) throw new Error("MmtpFecSession: updateAlFecFlow() needs a session built from an AL-FEC entry");
8222
+ const next = alFecRaptorQFlowConfig(flow);
8223
+ if (!next || next.sourcePacketIds[0] !== held.sourcePacketIds[0] || next.repairFlowId !== held.repairFlowId || next.fecCodeId !== held.fecCodeId || next.maximumK !== held.maximumK || next.repairSymbolSize !== held.repairSymbolSize) {
8224
+ return "refused";
8225
+ }
8226
+ if (next.maximumP <= this.#repairSymbols) return "unchanged";
8227
+ this.#repairSymbols = next.maximumP;
8228
+ this.#heldLayers = layerZero(next.maximumP);
8229
+ return "raised";
8153
8230
  }
8154
8231
  /** True once release() ran. */
8155
8232
  get released() {
@@ -8244,7 +8321,7 @@ var MmtpFecSession = class {
8244
8321
  }
8245
8322
  if (admission.reject) {
8246
8323
  const verdict2 = this.#heldLayers.failsClosed(
8247
- repairTrackName,
8324
+ this.#trackFor(repairTrackName),
8248
8325
  frame.ssStart,
8249
8326
  frame.rsbLength,
8250
8327
  this.#anchorHorizon,
@@ -8259,7 +8336,7 @@ var MmtpFecSession = class {
8259
8336
  this.#repairPacketId ??= frame.packetId;
8260
8337
  if (frame.ssbLength > this.sourceSymbols) return this.#drop("repair:ssb-length");
8261
8338
  const verdict = this.#heldLayers.admit(
8262
- repairTrackName,
8339
+ this.#trackFor(repairTrackName),
8263
8340
  frame.ssStart,
8264
8341
  frame.rsbLength,
8265
8342
  this.#anchorHorizon,
@@ -8318,6 +8395,10 @@ var MmtpFecSession = class {
8318
8395
  decoder.free();
8319
8396
  return true;
8320
8397
  }
8398
+ /** Catalog-less: layer 0 has no own-track clause (mmt-02 8.3), so no track name is judged. */
8399
+ #trackFor(repairTrackName) {
8400
+ return this.#message ? void 0 : repairTrackName;
8401
+ }
8321
8402
  #drop(reason) {
8322
8403
  this.#stats.dropped[reason] = (this.#stats.dropped[reason] ?? 0) + 1;
8323
8404
  return [];
@@ -8439,6 +8520,22 @@ var MmtpFecSession = class {
8439
8520
  }
8440
8521
  }
8441
8522
  };
8523
+ function layerZero(total) {
8524
+ return new HeldLayerAdmission([{ layer: 0, repairTrackName: "", rsIdOffset: 0, repairSymbols: total }]);
8525
+ }
8526
+ function buildDecoder(Decoder, session) {
8527
+ if (typeof Decoder !== "function") {
8528
+ throw new Error("MmtpFecSession: a decoder constructor (wasm_bindgen.MmtFecDecoder) is required");
8529
+ }
8530
+ const decoder = new Decoder(session.retentionBlocks, session.blockProfile, session.algorithm);
8531
+ try {
8532
+ decoder.configure_params(session.sourceSymbols, session.symbolSize);
8533
+ } catch (error) {
8534
+ decoder.free();
8535
+ throw error;
8536
+ }
8537
+ return decoder;
8538
+ }
8442
8539
  function requirePositiveInteger(value, path) {
8443
8540
  if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
8444
8541
  throw new Error(`MmtpFecSession: ${path} must be a positive integer (got ${String(value)})`);