@blockcast/mmt-container 1.0.1 → 2.0.0

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 (100) hide show
  1. package/README.md +9 -7
  2. package/dist/browser-runtime.js +6931 -6685
  3. package/dist/browser-runtime.js.map +4 -4
  4. package/dist/carp-catalog.d.ts +7 -7
  5. package/dist/carp-catalog.d.ts.map +1 -1
  6. package/dist/carp-catalog.js +65 -230
  7. package/dist/carp-catalog.js.map +3 -3
  8. package/dist/catalog-fec-params.js.map +1 -1
  9. package/dist/catalog-network-source.js.map +1 -1
  10. package/dist/delivery-catalog.js.map +1 -1
  11. package/dist/delta-fallback-config.js.map +1 -1
  12. package/dist/fec-manager.d.ts +25 -1
  13. package/dist/fec-manager.d.ts.map +1 -1
  14. package/dist/fec-manager.js +208 -15
  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.d.ts +8 -0
  19. package/dist/held-layer-admission.d.ts.map +1 -1
  20. package/dist/held-layer-admission.js +22 -1
  21. package/dist/held-layer-admission.js.map +2 -2
  22. package/dist/iso-block-locator.js.map +1 -1
  23. package/dist/mfu-reassembler.js.map +1 -1
  24. package/dist/mmtp-container-parser.js.map +1 -1
  25. package/dist/mmtp-container-parser.no-modules.js +127 -322
  26. package/dist/mmtp-container-parser.no-modules.js.map +4 -4
  27. package/dist/mmtp-decode-pipeline.js.map +1 -1
  28. package/dist/mmtp-fec-session.d.ts.map +1 -1
  29. package/dist/mmtp-fec-session.js +61 -15
  30. package/dist/mmtp-fec-session.js.map +3 -3
  31. package/dist/mmtp-router.d.ts +11 -6
  32. package/dist/mmtp-router.d.ts.map +1 -1
  33. package/dist/mmtp-router.js +47 -10
  34. package/dist/mmtp-router.js.map +3 -3
  35. package/dist/multicast-auth-catalog.d.ts +8 -8
  36. package/dist/multicast-auth-catalog.d.ts.map +1 -1
  37. package/dist/multicast-auth-catalog.js +171 -75
  38. package/dist/multicast-auth-catalog.js.map +4 -4
  39. package/dist/multicast-input.d.ts +5 -5
  40. package/dist/multicast-input.d.ts.map +1 -1
  41. package/dist/multicast-input.js +126 -49
  42. package/dist/multicast-input.js.map +3 -3
  43. package/dist/multicast-manager.d.ts +11 -4
  44. package/dist/multicast-manager.d.ts.map +1 -1
  45. package/dist/multicast-manager.js +7922 -7700
  46. package/dist/multicast-manager.js.map +4 -4
  47. package/dist/multicast-transport.d.ts +8 -0
  48. package/dist/multicast-transport.d.ts.map +1 -1
  49. package/dist/multicast-transport.js +27 -3
  50. package/dist/multicast-transport.js.map +3 -3
  51. package/dist/multicast-unwrap.js +2 -2
  52. package/dist/multicast-unwrap.js.map +2 -2
  53. package/dist/no-modules.js +81 -278
  54. package/dist/no-modules.js.map +4 -4
  55. package/dist/repair-fetch.js.map +1 -1
  56. package/dist/signaling.js.map +1 -1
  57. package/dist/timestamp-codec.js.map +1 -1
  58. package/dist/wasm-mfu-reassembler.js.map +1 -1
  59. package/dist/wasm-mmtp-container-parser.js.map +1 -1
  60. package/package.json +5 -5
  61. package/schemas/catalog.schema.json +15 -7
  62. package/src/carp-catalog.ts +14 -13
  63. package/src/fec-manager.ts +89 -14
  64. package/src/held-layer-admission.ts +38 -1
  65. package/src/mmtp-fec-session.ts +20 -12
  66. package/src/mmtp-router.ts +65 -16
  67. package/src/multicast-auth-catalog.ts +43 -36
  68. package/src/multicast-input.ts +28 -8
  69. package/src/multicast-manager.ts +38 -34
  70. package/src/multicast-transport.ts +31 -3
  71. package/test-vectors/bc-provenance-v2.json +614 -175
  72. package/test-vectors/catalog/README.md +24 -8
  73. package/test-vectors/catalog/negative/multicast-auth-asm-missing-sender-address.json +0 -2
  74. package/test-vectors/catalog/negative/{multicast-auth-manifest-port-equals-port.json → multicast-auth-draft01-manifest-port.json} +1 -1
  75. package/test-vectors/catalog/negative/multicast-auth-duplicate-first-epoch.json +0 -1
  76. package/test-vectors/catalog/negative/multicast-auth-duplicate-kid.json +0 -1
  77. package/test-vectors/catalog/negative/multicast-auth-endpoint-auth-without-object-auth.json +0 -1
  78. package/test-vectors/catalog/negative/multicast-auth-endpoint-missing-bandwidth.json +0 -1
  79. package/test-vectors/catalog/negative/multicast-auth-manifest-timer-out-of-range.json +0 -1
  80. package/test-vectors/catalog/negative/multicast-auth-sender-address-on-ssm.json +0 -1
  81. package/test-vectors/catalog/negative/multicast-auth-shared-sg-port.json +73 -0
  82. package/test-vectors/catalog/negative/multicast-auth-version-1.json +0 -1
  83. package/test-vectors/catalog/negative/multicast-auth-version-3.json +0 -1
  84. package/test-vectors/catalog/negative/multicast-auth-version-missing.json +0 -1
  85. package/test-vectors/catalog/negative/multicast-endpoint-address-family-mismatch.json +55 -0
  86. package/test-vectors/catalog/negative/multicast-endpoint-address-host-name.json +55 -0
  87. package/test-vectors/catalog/negative/multicast-endpoint-address-leading-zero.json +55 -0
  88. package/test-vectors/catalog/negative/multicast-endpoint-address-ls32-leading-zero.json +55 -0
  89. package/test-vectors/catalog/negative/multicast-endpoint-address-mapped-family-mismatch.json +55 -0
  90. package/test-vectors/catalog/negative/multicast-endpoint-address-zone-id.json +55 -0
  91. package/test-vectors/catalog/positive/multicast-auth-asm-sender.json +0 -2
  92. package/test-vectors/catalog/{negative/multicast-auth-max-auth-delay-above-max.json → positive/multicast-auth-max-auth-delay-above-max-clamped.json} +0 -1
  93. package/test-vectors/catalog/{negative/multicast-auth-max-auth-delay-below-min.json → positive/multicast-auth-max-auth-delay-below-min-clamped.json} +0 -1
  94. package/test-vectors/catalog/positive/multicast-auth-max-auth-delay-string.json +55 -0
  95. package/test-vectors/catalog/positive/multicast-auth-rotation.json +0 -1
  96. package/test-vectors/catalog/positive/multicast-draft01-7-2-6-example.json +0 -1
  97. package/test-vectors/catalog/section-codes.json +8 -3
  98. package/test-vectors/catalog-multicast-auth-v1.json +0 -1
  99. package/test-vectors/catalog-validation.json +3 -3
  100. package/test-vectors/generate-bc-provenance-v2.mjs +94 -14
@@ -319,14 +319,11 @@ function readRepairFecPayloadIdFields(mmtpPacket, parsedHeader) {
319
319
  dataOffset: off + REPAIR_FEC_PID_SIZE
320
320
  };
321
321
  }
322
- function parseMoqRepairFrameWithHeader(frame, header) {
323
- return moqRepairFrameFrom(frame, readRepairFecPayloadIdFields(frame, header));
324
- }
325
- function moqRepairFrameFrom(frame, repairId) {
322
+ function moqRepairFrameFrom(frame, repairId, checkRsId = true) {
326
323
  if (!repairId)
327
324
  return null;
328
325
  const symbolData = frame.subarray(repairId.dataOffset);
329
- if (repairId.rsbLength === 0 || repairId.ssbLength === 0 || repairId.rsId >= repairId.rsbLength || symbolData.length === 0)
326
+ if (repairId.ssbLength === 0 || checkRsId && repairId.rsId >= repairId.rsbLength || symbolData.length === 0)
330
327
  return null;
331
328
  return {
332
329
  packetId: repairId.packetId,
@@ -337,6 +334,23 @@ function moqRepairFrameFrom(frame, repairId) {
337
334
  symbolData
338
335
  };
339
336
  }
337
+ function admitMmtpRepairObject(packet, header, symbolSize, algorithm) {
338
+ if (header.packetType !== PACKET_TYPE_REPAIR || header.fecType !== 2)
339
+ return null;
340
+ if (header.headerLength !== MMTP_HEADER_SIZE || packet.length !== MMTP_HEADER_SIZE + REPAIR_FEC_PID_SIZE + symbolSize) {
341
+ return { reject: "repairLength" };
342
+ }
343
+ const frame = moqRepairFrameFrom(packet, readRepairFecPayloadIdFields(packet, header), false);
344
+ if (!frame)
345
+ return null;
346
+ if (frame.rsId >= frame.rsbLength)
347
+ return { frame, reject: "repairRsId" };
348
+ const blockSymbols = frame.ssbLength + frame.rsbLength;
349
+ if (algorithm === "reed-solomon" ? blockSymbols > 255 : algorithm === "raptorq" && blockSymbols - 1 > 16777215) {
350
+ return { frame, reject: "repairSchemeBound" };
351
+ }
352
+ return { frame };
353
+ }
340
354
 
341
355
  // ../mmt-base/dist/net-constants.js
342
356
  var DEFAULT_AMT_RELAY_PORT = 2268;
@@ -414,254 +428,6 @@ function isoBlocksBehindLowerBound(a, b, kMax) {
414
428
  return serial32Precedes(a, b) ? Math.floor(serial32Distance(a, b) / kMax) : 0;
415
429
  }
416
430
 
417
- // ../mmt-base/dist/multicast-catalog.js
418
- var ED25519_PUBLIC_KEY_BASE64URL = /^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$/;
419
- var UINT32_MAX = 4294967295;
420
- var AUTH_MEMBERS = /* @__PURE__ */ new Set(["scheme", "version", "sig", "keys", "minEpoch"]);
421
- var KEY_MEMBERS = /* @__PURE__ */ new Set(["kid", "publicKey", "firstEpoch"]);
422
- var ENDPOINT_AUTH_MEMBERS = /* @__PURE__ */ new Set([
423
- "manifestPort",
424
- "streamId",
425
- "senderAddress",
426
- "maxAuthDelayMs",
427
- "manifestTimerMs",
428
- "manifestTrack"
429
- ]);
430
- function isObject(value) {
431
- return typeof value === "object" && value !== null && !Array.isArray(value);
432
- }
433
- function isNonEmptyString(value) {
434
- return typeof value === "string" && value.trim().length > 0;
435
- }
436
- function isIntegerIn(value, min, max) {
437
- return Number.isSafeInteger(value) && value >= min && value <= max;
438
- }
439
- function multicastEndpointSource(endpoint) {
440
- if (!isObject(endpoint))
441
- return void 0;
442
- const sourceAddress = endpoint["sourceAddress"];
443
- if (sourceAddress !== void 0 && !isNonEmptyString(sourceAddress))
444
- return void 0;
445
- const mode = sourceAddress === void 0 ? "asm" : "ssm";
446
- const protocol = endpoint["protocol"];
447
- if (protocol !== void 0 && protocol !== mode)
448
- return void 0;
449
- const auth = endpoint["auth"];
450
- const senderAddress = isObject(auth) ? auth["senderAddress"] : void 0;
451
- if (mode === "ssm")
452
- return senderAddress === void 0 ? { mode, source: sourceAddress } : void 0;
453
- if (auth === void 0)
454
- return { mode };
455
- return isNonEmptyString(senderAddress) ? { mode, source: senderAddress } : void 0;
456
- }
457
- function authIssues(auth) {
458
- const base = "$.multicast.auth";
459
- const issue = (path, message) => [
460
- { code: "multicast_auth", path: `${base}${path}`, message }
461
- ];
462
- if (!isObject(auth))
463
- return issue("", "auth must be an object");
464
- const unknown = Object.keys(auth).filter((key) => !AUTH_MEMBERS.has(key));
465
- if (unknown.length > 0) {
466
- return issue("", `auth has unknown member(s) ${unknown.join(", ")}; draft -01 7.2.6 defines scheme, version, sig, keys and minEpoch`);
467
- }
468
- if (auth["scheme"] !== "bc-provenance")
469
- return issue(".scheme", 'scheme must be "bc-provenance"');
470
- if (auth["version"] !== 2)
471
- return issue(".version", "version must be 2 (bc-provenance version 2, draft -01 7.2)");
472
- if (auth["sig"] !== "ed25519")
473
- return issue(".sig", 'sig must be "ed25519"');
474
- const keys = auth["keys"];
475
- if (!Array.isArray(keys) || keys.length < 1 || keys.length > 4) {
476
- return issue(".keys", "keys must be an array of one to four {kid, publicKey, firstEpoch} objects");
477
- }
478
- const kids = /* @__PURE__ */ new Set();
479
- const firstEpochs = /* @__PURE__ */ new Set();
480
- for (let index = 0; index < keys.length; index++) {
481
- const key = keys[index];
482
- const at = `.keys[${index}]`;
483
- if (!isObject(key) || Object.keys(key).some((member) => !KEY_MEMBERS.has(member))) {
484
- return issue(at, "a key is exactly {kid, publicKey, firstEpoch}");
485
- }
486
- if (!isIntegerIn(key["kid"], 0, UINT32_MAX))
487
- return issue(`${at}.kid`, "kid must be an unsigned 32-bit integer");
488
- if (typeof key["publicKey"] !== "string" || !ED25519_PUBLIC_KEY_BASE64URL.test(key["publicKey"])) {
489
- return issue(`${at}.publicKey`, "publicKey must be a 32-octet Ed25519 key in canonical unpadded base64url");
490
- }
491
- if (!isIntegerIn(key["firstEpoch"], 0, UINT32_MAX)) {
492
- return issue(`${at}.firstEpoch`, "firstEpoch must be an unsigned 32-bit integer");
493
- }
494
- if (kids.has(key["kid"]))
495
- return issue(`${at}.kid`, `kid ${key["kid"]} is repeated; the catalog holds no key set`);
496
- if (firstEpochs.has(key["firstEpoch"])) {
497
- return issue(`${at}.firstEpoch`, `firstEpoch ${key["firstEpoch"]} is repeated; the catalog holds no key set`);
498
- }
499
- kids.add(key["kid"]);
500
- firstEpochs.add(key["firstEpoch"]);
501
- }
502
- if (!isIntegerIn(auth["minEpoch"], 0, UINT32_MAX)) {
503
- return issue(".minEpoch", "minEpoch must be an unsigned 32-bit integer");
504
- }
505
- return [];
506
- }
507
- function endpointAuthIssue(endpoint, mode, at) {
508
- const auth = endpoint["auth"];
509
- if (!isObject(auth))
510
- return { path: `${at}.auth`, message: "auth must be an object" };
511
- const unknown = Object.keys(auth).filter((key) => !ENDPOINT_AUTH_MEMBERS.has(key));
512
- if (unknown.length > 0)
513
- return { path: `${at}.auth`, message: `auth has unknown member(s) ${unknown.join(", ")}` };
514
- if (!isIntegerIn(auth["manifestPort"], 1, 65535)) {
515
- return { path: `${at}.auth.manifestPort`, message: "manifestPort must be an integer from 1 to 65535" };
516
- }
517
- if (!isIntegerIn(auth["streamId"], 0, UINT32_MAX)) {
518
- return { path: `${at}.auth.streamId`, message: "streamId must be an unsigned 32-bit integer" };
519
- }
520
- if (mode === "ssm" && auth["senderAddress"] !== void 0) {
521
- return { path: `${at}.auth.senderAddress`, message: "senderAddress must not appear on an SSM endpoint; S is sourceAddress" };
522
- }
523
- if (mode === "asm" && !isNonEmptyString(auth["senderAddress"])) {
524
- return { path: `${at}.auth.senderAddress`, message: "a protected ASM endpoint requires senderAddress" };
525
- }
526
- if (auth["maxAuthDelayMs"] !== void 0 && !isIntegerIn(auth["maxAuthDelayMs"], 50, 1e3)) {
527
- return { path: `${at}.auth.maxAuthDelayMs`, message: "maxAuthDelayMs must be an integer from 50 to 1000" };
528
- }
529
- if (auth["manifestTimerMs"] !== void 0 && !isIntegerIn(auth["manifestTimerMs"], 1, Number.MAX_SAFE_INTEGER)) {
530
- return { path: `${at}.auth.manifestTimerMs`, message: "manifestTimerMs must be an integer of at least 1" };
531
- }
532
- if (auth["manifestTrack"] !== void 0 && !isNonEmptyString(auth["manifestTrack"])) {
533
- return { path: `${at}.auth.manifestTrack`, message: "manifestTrack must be a non-empty string" };
534
- }
535
- if (!isIntegerIn(endpoint["bandwidth"], 1, Number.MAX_SAFE_INTEGER)) {
536
- return { path: `${at}.bandwidth`, message: "a protected endpoint requires bandwidth, which bounds the receiver's hold queues" };
537
- }
538
- return void 0;
539
- }
540
- var CATALOG_NAMESPACE = /* @__PURE__ */ Symbol.for("@blockcast/mmt-base/catalogNamespace");
541
- function multicastCatalogIssues(catalog, options = {}) {
542
- if (!isObject(catalog))
543
- return [];
544
- const issues = [];
545
- if ("multicast.auth" in catalog) {
546
- issues.push({
547
- code: "multicast_auth",
548
- path: '$["multicast.auth"]',
549
- message: 'the root "multicast.auth" key is retired; declare auth as multicast.auth (draft -01 7.2.6)'
550
- });
551
- }
552
- const multicast = catalog["multicast"];
553
- if (!isObject(multicast))
554
- return issues;
555
- const protectedCatalog = multicast["auth"] !== void 0;
556
- if (protectedCatalog)
557
- issues.push(...authIssues(multicast["auth"]));
558
- const endpoints = multicast["endpoints"];
559
- if (!Array.isArray(endpoints))
560
- return issues;
561
- const tracks = Array.isArray(catalog["tracks"]) ? catalog["tracks"] : [];
562
- const namespaceOf = /* @__PURE__ */ new Map();
563
- for (const track of tracks) {
564
- if (isObject(track) && typeof track["name"] === "string")
565
- namespaceOf.set(track["name"], track["namespace"]);
566
- }
567
- const tagged = catalog[CATALOG_NAMESPACE];
568
- let referenceNamespace = options.catalogNamespace ?? (typeof tagged === "string" ? tagged : void 0);
569
- const sources = [];
570
- for (let index = 0; index < endpoints.length; index++) {
571
- const endpoint = endpoints[index];
572
- const at = `$.multicast.endpoints[${index}]`;
573
- const fail = (code, path, message) => {
574
- issues.push({ code, path, message, endpointIndex: index });
575
- };
576
- sources.push(void 0);
577
- if (!isObject(endpoint)) {
578
- fail("multicast_endpoint", at, "endpoint must be an object");
579
- continue;
580
- }
581
- const protocol = endpoint["protocol"];
582
- if (protocol !== void 0 && protocol !== "ssm" && protocol !== "asm") {
583
- fail("multicast_endpoint", `${at}.protocol`, `protocol must be "ssm" or "asm"; a bearer such as ${JSON.stringify(protocol)} is a networkSource entry (draft -01 4.2)`);
584
- continue;
585
- }
586
- const hasAuth = endpoint["auth"] !== void 0;
587
- if (protectedCatalog !== hasAuth) {
588
- fail("multicast_endpoint_auth", `${at}.auth`, protectedCatalog ? "every endpoint requires auth when multicast carries auth; a receiver ignores this endpoint" : "auth names no key without multicast.auth; a receiver ignores this endpoint");
589
- continue;
590
- }
591
- const sourceAddress = endpoint["sourceAddress"];
592
- if (sourceAddress !== void 0 && !isNonEmptyString(sourceAddress)) {
593
- fail("multicast_endpoint", `${at}.sourceAddress`, "sourceAddress must be a non-empty string");
594
- continue;
595
- }
596
- const mode = sourceAddress === void 0 ? "asm" : "ssm";
597
- if (protocol !== void 0 && protocol !== mode) {
598
- fail("multicast_endpoint", `${at}.protocol`, `protocol ${JSON.stringify(protocol)} disagrees with sourceAddress, which selects ${mode}`);
599
- continue;
600
- }
601
- if (!isIntegerIn(endpoint["port"], 1, 65535)) {
602
- fail("multicast_endpoint", `${at}.port`, "port must be an integer from 1 to 65535");
603
- continue;
604
- }
605
- if (hasAuth) {
606
- const authIssue = endpointAuthIssue(endpoint, mode, at);
607
- if (authIssue) {
608
- fail("multicast_endpoint_auth", authIssue.path, authIssue.message);
609
- continue;
610
- }
611
- }
612
- sources[index] = multicastEndpointSource(endpoint);
613
- const refs = Array.isArray(endpoint["tracks"]) ? endpoint["tracks"] : [];
614
- for (let refIndex = 0; refIndex < refs.length; refIndex++) {
615
- const ref = refs[refIndex];
616
- const name = isObject(ref) ? ref["name"] : ref;
617
- if (typeof name !== "string")
618
- continue;
619
- const namespace = namespaceOf.get(name);
620
- if (typeof namespace !== "string")
621
- continue;
622
- referenceNamespace ??= namespace;
623
- if (namespace !== referenceNamespace) {
624
- fail("multicast_track", `${at}.tracks[${refIndex}].name`, `track ${JSON.stringify(name)} declares namespace ${JSON.stringify(namespace)}, not the catalog track's ${JSON.stringify(referenceNamespace)}`);
625
- }
626
- }
627
- }
628
- const tupleOf = (index) => {
629
- const endpoint = endpoints[index];
630
- const auth = endpoint["auth"];
631
- return {
632
- sg: JSON.stringify([sources[index].source ?? null, endpoint["groupAddress"]]),
633
- port: endpoint["port"],
634
- manifestPort: isObject(auth) ? auth["manifestPort"] : void 0
635
- };
636
- };
637
- for (let index = 0; index < endpoints.length; index++) {
638
- if (!sources[index])
639
- continue;
640
- const own = tupleOf(index);
641
- const at = `$.multicast.endpoints[${index}]`;
642
- if (own.manifestPort !== void 0 && own.manifestPort === own.port) {
643
- issues.push({ code: "multicast_endpoint_auth", path: `${at}.auth.manifestPort`, message: "manifestPort must differ from the endpoint's own port", endpointIndex: index });
644
- continue;
645
- }
646
- for (let other = 0; other < endpoints.length; other++) {
647
- if (other === index || !sources[other])
648
- continue;
649
- const peer = tupleOf(other);
650
- if (peer.sg !== own.sg)
651
- continue;
652
- if (peer.port === own.port) {
653
- issues.push({ code: "multicast_endpoint", path: `${at}.port`, message: `(S, G, port) is shared with endpoint ${other}; one endpoint object per multicast tuple`, endpointIndex: index });
654
- break;
655
- }
656
- if (own.manifestPort !== void 0 && (own.manifestPort === peer.port || own.manifestPort === peer.manifestPort)) {
657
- issues.push({ code: "multicast_endpoint_auth", path: `${at}.auth.manifestPort`, message: `manifestPort is a port or manifestPort of endpoint ${other} on the same (S, G)`, endpointIndex: index });
658
- break;
659
- }
660
- }
661
- }
662
- return issues;
663
- }
664
-
665
431
  // ../mmt-base/dist/fec-catalog.js
666
432
  var DEFAULT_SCHEMES = ["reed-solomon", "raptorq"];
667
433
  var DEFAULT_PROFILES = ["moq-object-v1", "iso-ssbg1-v1"];
@@ -7962,7 +7728,7 @@ function validateNetworkSourceRelays(sources, path) {
7962
7728
  }
7963
7729
  });
7964
7730
  }
7965
- function validateCmsfMmtpCatalog(value, options = {}) {
7731
+ function validateCmsfMmtpCatalog(value, _options = {}) {
7966
7732
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
7967
7733
  catalogError("catalog_object", "$", "catalog must be an object");
7968
7734
  }
@@ -8153,8 +7919,6 @@ function validateCmsfMmtpCatalog(value, options = {}) {
8153
7919
  }
8154
7920
  }
8155
7921
  }
8156
- const [multicastIssue] = multicastCatalogIssues(catalog, options);
8157
- if (multicastIssue) catalogError(multicastIssue.code, multicastIssue.path, multicastIssue.message);
8158
7922
  validateArchive(catalog);
8159
7923
  return catalog;
8160
7924
  }
@@ -8233,6 +7997,9 @@ var HeldLayerAdmission = class {
8233
7997
  * @returns undefined to admit, `'discard'`, or the fail-closed reason.
8234
7998
  */
8235
7999
  admit(repairTrackName, ssStart, rsbLength, horizon, sourceFrontier) {
8000
+ return this.#admit(repairTrackName, ssStart, rsbLength, horizon, sourceFrontier, true);
8001
+ }
8002
+ #admit(repairTrackName, ssStart, rsbLength, horizon, sourceFrontier, retroactive) {
8236
8003
  if (repairTrackName !== void 0 && !this.#layerNames.has(repairTrackName)) return "discard";
8237
8004
  const farAhead = sourceFrontier !== void 0 && serial32Precedes(sourceFrontier, ssStart) && serial32Distance(sourceFrontier, ssStart) > horizon;
8238
8005
  for (const state of this.#states) {
@@ -8260,7 +8027,7 @@ var HeldLayerAdmission = class {
8260
8027
  }
8261
8028
  } else if (!state.settled && !stale(ssStart)) {
8262
8029
  if (state.anchor === void 0 || serial32Precedes(ssStart, state.anchor)) state.anchor = ssStart;
8263
- if (state.latestBelow !== void 0 && !serial32Precedes(state.latestBelow, state.anchor)) {
8030
+ if (retroactive && state.latestBelow !== void 0 && !serial32Precedes(state.latestBelow, state.anchor)) {
8264
8031
  code = "below_anchored_total";
8265
8032
  ssStart = state.latestBelow;
8266
8033
  rsbLength = state.latestBelowRsb;
@@ -8280,6 +8047,24 @@ var HeldLayerAdmission = class {
8280
8047
  }
8281
8048
  return void 0;
8282
8049
  }
8050
+ /**
8051
+ * The core-00 7.5 carve-out for a repair object about to be discarded
8052
+ * (RS_ID or scheme bound): whether its own RSB_length, below a held layer's
8053
+ * total on that layer's track or on an anchored block, fails the instance
8054
+ * closed under 7.3.2. Leaves the anchored state as it was: a discarded
8055
+ * object anchors nothing.
8056
+ */
8057
+ failsClosed(repairTrackName, ssStart, rsbLength, horizon, sourceFrontier) {
8058
+ const saved = this.#states.map((state) => ({ ...state }));
8059
+ try {
8060
+ const verdict = this.#admit(repairTrackName, ssStart, rsbLength, horizon, sourceFrontier, false);
8061
+ return typeof verdict === "object" ? verdict : void 0;
8062
+ } finally {
8063
+ saved.forEach((state, i) => {
8064
+ this.#states[i] = state;
8065
+ });
8066
+ }
8067
+ }
8283
8068
  };
8284
8069
 
8285
8070
  // src/mmtp-fec-session.ts
@@ -8450,18 +8235,29 @@ var MmtpFecSession = class {
8450
8235
  } catch {
8451
8236
  return this.#drop("repair:malformed");
8452
8237
  }
8453
- const frame = parseMoqRepairFrameWithHeader(packet, header);
8454
- if (!frame) return this.#drop("repair:malformed");
8455
- if (this.#repairPacketId === void 0) {
8456
- this.#repairPacketId = frame.packetId;
8457
- } else if (frame.packetId !== this.#repairPacketId) {
8238
+ const admission = admitMmtpRepairObject(packet, header, this.symbolSize, this.algorithm);
8239
+ if (!admission) return this.#drop("repair:malformed");
8240
+ if (!("frame" in admission)) return this.#drop("repair:length");
8241
+ const frame = admission.frame;
8242
+ if (this.#repairPacketId !== void 0 && frame.packetId !== this.#repairPacketId) {
8458
8243
  return this.#drop("repair:foreign-packet-id");
8459
8244
  }
8460
- if (frame.symbolData.byteLength !== this.symbolSize) return this.#drop("repair:symbol-size");
8461
- if (frame.ssbLength > this.sourceSymbols) return this.#drop("repair:ssb-length");
8462
- if (this.algorithm === "reed-solomon" && frame.ssbLength + frame.rsbLength > 255) {
8463
- return this.#drop("repair:rs-block-length");
8245
+ if (admission.reject) {
8246
+ const verdict2 = this.#heldLayers.failsClosed(
8247
+ repairTrackName,
8248
+ frame.ssStart,
8249
+ frame.rsbLength,
8250
+ this.#anchorHorizon,
8251
+ this.#newestSource
8252
+ );
8253
+ if (verdict2) {
8254
+ this.#stats.failClosed = verdict2;
8255
+ return this.#drop("repair:fail-closed");
8256
+ }
8257
+ return this.#drop(admission.reject === "repairRsId" ? "repair:rs-id" : "repair:scheme-bound");
8464
8258
  }
8259
+ this.#repairPacketId ??= frame.packetId;
8260
+ if (frame.ssbLength > this.sourceSymbols) return this.#drop("repair:ssb-length");
8465
8261
  const verdict = this.#heldLayers.admit(
8466
8262
  repairTrackName,
8467
8263
  frame.ssStart,
@@ -8736,8 +8532,8 @@ function u32(v) {
8736
8532
  }
8737
8533
  var KEYFRAME_FLAGS = 33554432;
8738
8534
  var DELTA_FLAGS = 16842752;
8739
- var UINT32_MAX2 = 4294967295;
8740
- var MAX_TRACK_ID = UINT32_MAX2 - 1;
8535
+ var UINT32_MAX = 4294967295;
8536
+ var MAX_TRACK_ID = UINT32_MAX - 1;
8741
8537
  var INT32_MIN = -2147483648;
8742
8538
  var INT32_MAX = 2147483647;
8743
8539
  function assertTrackId(trackId, label) {
@@ -8746,8 +8542,8 @@ function assertTrackId(trackId, label) {
8746
8542
  }
8747
8543
  }
8748
8544
  function assertTimescale(timescale, label) {
8749
- if (!Number.isInteger(timescale) || timescale <= 0 || timescale > UINT32_MAX2) {
8750
- throw new RangeError(`${label} must be an integer in 1..${UINT32_MAX2}, got ${timescale}`);
8545
+ if (!Number.isInteger(timescale) || timescale <= 0 || timescale > UINT32_MAX) {
8546
+ throw new RangeError(`${label} must be an integer in 1..${UINT32_MAX}, got ${timescale}`);
8751
8547
  }
8752
8548
  }
8753
8549
  function assertCompositionTimeOffsets(samples) {
@@ -10060,6 +9856,11 @@ function parseIpv6(address) {
10060
9856
  const [leftText, rightText = ""] = address.split("::");
10061
9857
  const left = leftText ? leftText.split(":") : [];
10062
9858
  const right = rightText ? rightText.split(":") : [];
9859
+ const tail = compressed ? right : left;
9860
+ if (tail.length > 0 && tail[tail.length - 1].includes(".")) {
9861
+ const v4 = parseIpv4(tail.pop());
9862
+ tail.push((v4[0] << 8 | v4[1]).toString(16), (v4[2] << 8 | v4[3]).toString(16));
9863
+ }
10063
9864
  if (left.some((group) => group.includes(".")) || right.some((group) => group.includes("."))) {
10064
9865
  throw new Error(`invalid IPv6 address ${address}`);
10065
9866
  }
@@ -10082,7 +9883,7 @@ function parseBcProvenanceV2Address(address) {
10082
9883
  return parseIpv4(address);
10083
9884
  }
10084
9885
  function endpointBytes(endpoint) {
10085
- if (!validPort(endpoint.port) || !validPort(endpoint.manifestPort) || !validU32(endpoint.streamId)) {
9886
+ if (!validPort(endpoint.port) || !validU32(endpoint.streamId)) {
10086
9887
  throw new Error("invalid BCPV2 endpoint");
10087
9888
  }
10088
9889
  const source = parseBcProvenanceV2Address(endpoint.sourceAddress);
@@ -10142,7 +9943,7 @@ function bcProvenanceV2SignedMessage(record, endpoint) {
10142
9943
  offset += source.byteLength;
10143
9944
  prefix.set(group, offset);
10144
9945
  offset += group.byteLength;
10145
- prefix.set(u16(endpoint.manifestPort), offset);
9946
+ prefix.set(u16(endpoint.port), offset);
10146
9947
  const message = new Uint8Array(prefix.byteLength + record.body.byteLength);
10147
9948
  message.set(prefix);
10148
9949
  message.set(record.body, prefix.byteLength);
@@ -11697,10 +11498,12 @@ var BcProvenanceV2Receiver = class {
11697
11498
  order = 0;
11698
11499
  buckets;
11699
11500
  constructor(cfg) {
11700
- this.maxAuthDelayMs = cfg.maxAuthDelayMs ?? 200;
11701
- this.manifestTimerMs = cfg.manifestTimerMs ?? 20;
11702
- if (!(this.maxAuthDelayMs >= 50 && this.maxAuthDelayMs <= 1e3) || !(this.manifestTimerMs >= 1)) {
11703
- throw new Error("BCPV2 endpoint ignored: maxAuthDelayMs or manifestTimerMs out of range");
11501
+ const delay = cfg.maxAuthDelayMs;
11502
+ this.maxAuthDelayMs = typeof delay !== "number" || !Number.isFinite(delay) ? 200 : delay < 50 ? 50 : delay > 1e3 ? 1e3 : Number.isInteger(delay) ? delay : 200;
11503
+ const timer = cfg.manifestTimerMs;
11504
+ this.manifestTimerMs = typeof timer !== "number" || !Number.isFinite(timer) ? 20 : timer < 1 || Number.isInteger(timer) ? timer : 20;
11505
+ if (!(this.manifestTimerMs >= 1)) {
11506
+ throw new Error("BCPV2 endpoint ignored: manifestTimerMs below 1");
11704
11507
  }
11705
11508
  if (!(cfg.bandwidth > 0))
11706
11509
  throw new Error("BCPV2 endpoint ignored: protected endpoint without bandwidth");
@@ -11740,7 +11543,7 @@ var BcProvenanceV2Receiver = class {
11740
11543
  return 0;
11741
11544
  return this.lead ?? BCPV2_LEAD_BOUND_MS;
11742
11545
  }
11743
- /** Step 1: a record datagram on `manifestPort`, or a record object of the manifest track. */
11546
+ /** Step 1: a record datagram on `port`, or a record object of the manifest track. */
11744
11547
  onRecord(bytes, path) {
11745
11548
  const now = this.now();
11746
11549
  this.expire(now);
@@ -12064,7 +11867,7 @@ var BcProvenanceV2Receiver = class {
12064
11867
  }
12065
11868
  /**
12066
11869
  * Retire verified entries below their arrival path's replay floor, keeping a mirrored entry
12067
- * while the freshness check would still pass its copy on `manifestPort`.
11870
+ * while the freshness check would still pass its copy on `port`.
12068
11871
  * ponytail: linear scan per record; index by (kid, epoch) if retained sets grow past a few thousand.
12069
11872
  */
12070
11873
  prune(now) {