@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
@@ -382,14 +382,11 @@ var BlockcastMmt = (() => {
382
382
  dataOffset: off + REPAIR_FEC_PID_SIZE
383
383
  };
384
384
  }
385
- function parseMoqRepairFrameWithHeader(frame, header) {
386
- return moqRepairFrameFrom(frame, readRepairFecPayloadIdFields(frame, header));
387
- }
388
- function moqRepairFrameFrom(frame, repairId) {
385
+ function moqRepairFrameFrom(frame, repairId, checkRsId = true) {
389
386
  if (!repairId)
390
387
  return null;
391
388
  const symbolData = frame.subarray(repairId.dataOffset);
392
- if (repairId.rsbLength === 0 || repairId.ssbLength === 0 || repairId.rsId >= repairId.rsbLength || symbolData.length === 0)
389
+ if (repairId.ssbLength === 0 || checkRsId && repairId.rsId >= repairId.rsbLength || symbolData.length === 0)
393
390
  return null;
394
391
  return {
395
392
  packetId: repairId.packetId,
@@ -400,6 +397,23 @@ var BlockcastMmt = (() => {
400
397
  symbolData
401
398
  };
402
399
  }
400
+ function admitMmtpRepairObject(packet, header, symbolSize, algorithm) {
401
+ if (header.packetType !== PACKET_TYPE_REPAIR || header.fecType !== 2)
402
+ return null;
403
+ if (header.headerLength !== MMTP_HEADER_SIZE || packet.length !== MMTP_HEADER_SIZE + REPAIR_FEC_PID_SIZE + symbolSize) {
404
+ return { reject: "repairLength" };
405
+ }
406
+ const frame = moqRepairFrameFrom(packet, readRepairFecPayloadIdFields(packet, header), false);
407
+ if (!frame)
408
+ return null;
409
+ if (frame.rsId >= frame.rsbLength)
410
+ return { frame, reject: "repairRsId" };
411
+ const blockSymbols = frame.ssbLength + frame.rsbLength;
412
+ if (algorithm === "reed-solomon" ? blockSymbols > 255 : algorithm === "raptorq" && blockSymbols - 1 > 16777215) {
413
+ return { frame, reject: "repairSchemeBound" };
414
+ }
415
+ return { frame };
416
+ }
403
417
 
404
418
  // ../mmt-base/dist/net-constants.js
405
419
  var DEFAULT_AMT_RELAY_PORT = 2268;
@@ -477,254 +491,6 @@ var BlockcastMmt = (() => {
477
491
  return serial32Precedes(a, b) ? Math.floor(serial32Distance(a, b) / kMax) : 0;
478
492
  }
479
493
 
480
- // ../mmt-base/dist/multicast-catalog.js
481
- var ED25519_PUBLIC_KEY_BASE64URL = /^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$/;
482
- var UINT32_MAX = 4294967295;
483
- var AUTH_MEMBERS = /* @__PURE__ */ new Set(["scheme", "version", "sig", "keys", "minEpoch"]);
484
- var KEY_MEMBERS = /* @__PURE__ */ new Set(["kid", "publicKey", "firstEpoch"]);
485
- var ENDPOINT_AUTH_MEMBERS = /* @__PURE__ */ new Set([
486
- "manifestPort",
487
- "streamId",
488
- "senderAddress",
489
- "maxAuthDelayMs",
490
- "manifestTimerMs",
491
- "manifestTrack"
492
- ]);
493
- function isObject(value) {
494
- return typeof value === "object" && value !== null && !Array.isArray(value);
495
- }
496
- function isNonEmptyString(value) {
497
- return typeof value === "string" && value.trim().length > 0;
498
- }
499
- function isIntegerIn(value, min, max) {
500
- return Number.isSafeInteger(value) && value >= min && value <= max;
501
- }
502
- function multicastEndpointSource(endpoint) {
503
- if (!isObject(endpoint))
504
- return void 0;
505
- const sourceAddress = endpoint["sourceAddress"];
506
- if (sourceAddress !== void 0 && !isNonEmptyString(sourceAddress))
507
- return void 0;
508
- const mode = sourceAddress === void 0 ? "asm" : "ssm";
509
- const protocol = endpoint["protocol"];
510
- if (protocol !== void 0 && protocol !== mode)
511
- return void 0;
512
- const auth = endpoint["auth"];
513
- const senderAddress = isObject(auth) ? auth["senderAddress"] : void 0;
514
- if (mode === "ssm")
515
- return senderAddress === void 0 ? { mode, source: sourceAddress } : void 0;
516
- if (auth === void 0)
517
- return { mode };
518
- return isNonEmptyString(senderAddress) ? { mode, source: senderAddress } : void 0;
519
- }
520
- function authIssues(auth) {
521
- const base = "$.multicast.auth";
522
- const issue = (path, message) => [
523
- { code: "multicast_auth", path: `${base}${path}`, message }
524
- ];
525
- if (!isObject(auth))
526
- return issue("", "auth must be an object");
527
- const unknown = Object.keys(auth).filter((key) => !AUTH_MEMBERS.has(key));
528
- if (unknown.length > 0) {
529
- return issue("", `auth has unknown member(s) ${unknown.join(", ")}; draft -01 7.2.6 defines scheme, version, sig, keys and minEpoch`);
530
- }
531
- if (auth["scheme"] !== "bc-provenance")
532
- return issue(".scheme", 'scheme must be "bc-provenance"');
533
- if (auth["version"] !== 2)
534
- return issue(".version", "version must be 2 (bc-provenance version 2, draft -01 7.2)");
535
- if (auth["sig"] !== "ed25519")
536
- return issue(".sig", 'sig must be "ed25519"');
537
- const keys = auth["keys"];
538
- if (!Array.isArray(keys) || keys.length < 1 || keys.length > 4) {
539
- return issue(".keys", "keys must be an array of one to four {kid, publicKey, firstEpoch} objects");
540
- }
541
- const kids = /* @__PURE__ */ new Set();
542
- const firstEpochs = /* @__PURE__ */ new Set();
543
- for (let index = 0; index < keys.length; index++) {
544
- const key = keys[index];
545
- const at = `.keys[${index}]`;
546
- if (!isObject(key) || Object.keys(key).some((member) => !KEY_MEMBERS.has(member))) {
547
- return issue(at, "a key is exactly {kid, publicKey, firstEpoch}");
548
- }
549
- if (!isIntegerIn(key["kid"], 0, UINT32_MAX))
550
- return issue(`${at}.kid`, "kid must be an unsigned 32-bit integer");
551
- if (typeof key["publicKey"] !== "string" || !ED25519_PUBLIC_KEY_BASE64URL.test(key["publicKey"])) {
552
- return issue(`${at}.publicKey`, "publicKey must be a 32-octet Ed25519 key in canonical unpadded base64url");
553
- }
554
- if (!isIntegerIn(key["firstEpoch"], 0, UINT32_MAX)) {
555
- return issue(`${at}.firstEpoch`, "firstEpoch must be an unsigned 32-bit integer");
556
- }
557
- if (kids.has(key["kid"]))
558
- return issue(`${at}.kid`, `kid ${key["kid"]} is repeated; the catalog holds no key set`);
559
- if (firstEpochs.has(key["firstEpoch"])) {
560
- return issue(`${at}.firstEpoch`, `firstEpoch ${key["firstEpoch"]} is repeated; the catalog holds no key set`);
561
- }
562
- kids.add(key["kid"]);
563
- firstEpochs.add(key["firstEpoch"]);
564
- }
565
- if (!isIntegerIn(auth["minEpoch"], 0, UINT32_MAX)) {
566
- return issue(".minEpoch", "minEpoch must be an unsigned 32-bit integer");
567
- }
568
- return [];
569
- }
570
- function endpointAuthIssue(endpoint, mode, at) {
571
- const auth = endpoint["auth"];
572
- if (!isObject(auth))
573
- return { path: `${at}.auth`, message: "auth must be an object" };
574
- const unknown = Object.keys(auth).filter((key) => !ENDPOINT_AUTH_MEMBERS.has(key));
575
- if (unknown.length > 0)
576
- return { path: `${at}.auth`, message: `auth has unknown member(s) ${unknown.join(", ")}` };
577
- if (!isIntegerIn(auth["manifestPort"], 1, 65535)) {
578
- return { path: `${at}.auth.manifestPort`, message: "manifestPort must be an integer from 1 to 65535" };
579
- }
580
- if (!isIntegerIn(auth["streamId"], 0, UINT32_MAX)) {
581
- return { path: `${at}.auth.streamId`, message: "streamId must be an unsigned 32-bit integer" };
582
- }
583
- if (mode === "ssm" && auth["senderAddress"] !== void 0) {
584
- return { path: `${at}.auth.senderAddress`, message: "senderAddress must not appear on an SSM endpoint; S is sourceAddress" };
585
- }
586
- if (mode === "asm" && !isNonEmptyString(auth["senderAddress"])) {
587
- return { path: `${at}.auth.senderAddress`, message: "a protected ASM endpoint requires senderAddress" };
588
- }
589
- if (auth["maxAuthDelayMs"] !== void 0 && !isIntegerIn(auth["maxAuthDelayMs"], 50, 1e3)) {
590
- return { path: `${at}.auth.maxAuthDelayMs`, message: "maxAuthDelayMs must be an integer from 50 to 1000" };
591
- }
592
- if (auth["manifestTimerMs"] !== void 0 && !isIntegerIn(auth["manifestTimerMs"], 1, Number.MAX_SAFE_INTEGER)) {
593
- return { path: `${at}.auth.manifestTimerMs`, message: "manifestTimerMs must be an integer of at least 1" };
594
- }
595
- if (auth["manifestTrack"] !== void 0 && !isNonEmptyString(auth["manifestTrack"])) {
596
- return { path: `${at}.auth.manifestTrack`, message: "manifestTrack must be a non-empty string" };
597
- }
598
- if (!isIntegerIn(endpoint["bandwidth"], 1, Number.MAX_SAFE_INTEGER)) {
599
- return { path: `${at}.bandwidth`, message: "a protected endpoint requires bandwidth, which bounds the receiver's hold queues" };
600
- }
601
- return void 0;
602
- }
603
- var CATALOG_NAMESPACE = /* @__PURE__ */ Symbol.for("@blockcast/mmt-base/catalogNamespace");
604
- function multicastCatalogIssues(catalog, options = {}) {
605
- if (!isObject(catalog))
606
- return [];
607
- const issues = [];
608
- if ("multicast.auth" in catalog) {
609
- issues.push({
610
- code: "multicast_auth",
611
- path: '$["multicast.auth"]',
612
- message: 'the root "multicast.auth" key is retired; declare auth as multicast.auth (draft -01 7.2.6)'
613
- });
614
- }
615
- const multicast = catalog["multicast"];
616
- if (!isObject(multicast))
617
- return issues;
618
- const protectedCatalog = multicast["auth"] !== void 0;
619
- if (protectedCatalog)
620
- issues.push(...authIssues(multicast["auth"]));
621
- const endpoints = multicast["endpoints"];
622
- if (!Array.isArray(endpoints))
623
- return issues;
624
- const tracks = Array.isArray(catalog["tracks"]) ? catalog["tracks"] : [];
625
- const namespaceOf = /* @__PURE__ */ new Map();
626
- for (const track of tracks) {
627
- if (isObject(track) && typeof track["name"] === "string")
628
- namespaceOf.set(track["name"], track["namespace"]);
629
- }
630
- const tagged = catalog[CATALOG_NAMESPACE];
631
- let referenceNamespace = options.catalogNamespace ?? (typeof tagged === "string" ? tagged : void 0);
632
- const sources = [];
633
- for (let index = 0; index < endpoints.length; index++) {
634
- const endpoint = endpoints[index];
635
- const at = `$.multicast.endpoints[${index}]`;
636
- const fail = (code, path, message) => {
637
- issues.push({ code, path, message, endpointIndex: index });
638
- };
639
- sources.push(void 0);
640
- if (!isObject(endpoint)) {
641
- fail("multicast_endpoint", at, "endpoint must be an object");
642
- continue;
643
- }
644
- const protocol = endpoint["protocol"];
645
- if (protocol !== void 0 && protocol !== "ssm" && protocol !== "asm") {
646
- 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)`);
647
- continue;
648
- }
649
- const hasAuth = endpoint["auth"] !== void 0;
650
- if (protectedCatalog !== hasAuth) {
651
- 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");
652
- continue;
653
- }
654
- const sourceAddress = endpoint["sourceAddress"];
655
- if (sourceAddress !== void 0 && !isNonEmptyString(sourceAddress)) {
656
- fail("multicast_endpoint", `${at}.sourceAddress`, "sourceAddress must be a non-empty string");
657
- continue;
658
- }
659
- const mode = sourceAddress === void 0 ? "asm" : "ssm";
660
- if (protocol !== void 0 && protocol !== mode) {
661
- fail("multicast_endpoint", `${at}.protocol`, `protocol ${JSON.stringify(protocol)} disagrees with sourceAddress, which selects ${mode}`);
662
- continue;
663
- }
664
- if (!isIntegerIn(endpoint["port"], 1, 65535)) {
665
- fail("multicast_endpoint", `${at}.port`, "port must be an integer from 1 to 65535");
666
- continue;
667
- }
668
- if (hasAuth) {
669
- const authIssue = endpointAuthIssue(endpoint, mode, at);
670
- if (authIssue) {
671
- fail("multicast_endpoint_auth", authIssue.path, authIssue.message);
672
- continue;
673
- }
674
- }
675
- sources[index] = multicastEndpointSource(endpoint);
676
- const refs = Array.isArray(endpoint["tracks"]) ? endpoint["tracks"] : [];
677
- for (let refIndex = 0; refIndex < refs.length; refIndex++) {
678
- const ref = refs[refIndex];
679
- const name = isObject(ref) ? ref["name"] : ref;
680
- if (typeof name !== "string")
681
- continue;
682
- const namespace = namespaceOf.get(name);
683
- if (typeof namespace !== "string")
684
- continue;
685
- referenceNamespace ?? (referenceNamespace = namespace);
686
- if (namespace !== referenceNamespace) {
687
- fail("multicast_track", `${at}.tracks[${refIndex}].name`, `track ${JSON.stringify(name)} declares namespace ${JSON.stringify(namespace)}, not the catalog track's ${JSON.stringify(referenceNamespace)}`);
688
- }
689
- }
690
- }
691
- const tupleOf = (index) => {
692
- const endpoint = endpoints[index];
693
- const auth = endpoint["auth"];
694
- return {
695
- sg: JSON.stringify([sources[index].source ?? null, endpoint["groupAddress"]]),
696
- port: endpoint["port"],
697
- manifestPort: isObject(auth) ? auth["manifestPort"] : void 0
698
- };
699
- };
700
- for (let index = 0; index < endpoints.length; index++) {
701
- if (!sources[index])
702
- continue;
703
- const own = tupleOf(index);
704
- const at = `$.multicast.endpoints[${index}]`;
705
- if (own.manifestPort !== void 0 && own.manifestPort === own.port) {
706
- issues.push({ code: "multicast_endpoint_auth", path: `${at}.auth.manifestPort`, message: "manifestPort must differ from the endpoint's own port", endpointIndex: index });
707
- continue;
708
- }
709
- for (let other = 0; other < endpoints.length; other++) {
710
- if (other === index || !sources[other])
711
- continue;
712
- const peer = tupleOf(other);
713
- if (peer.sg !== own.sg)
714
- continue;
715
- if (peer.port === own.port) {
716
- 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 });
717
- break;
718
- }
719
- if (own.manifestPort !== void 0 && (own.manifestPort === peer.port || own.manifestPort === peer.manifestPort)) {
720
- 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 });
721
- break;
722
- }
723
- }
724
- }
725
- return issues;
726
- }
727
-
728
494
  // ../mmt-base/dist/fec-catalog.js
729
495
  var DEFAULT_SCHEMES = ["reed-solomon", "raptorq"];
730
496
  var DEFAULT_PROFILES = ["moq-object-v1", "iso-ssbg1-v1"];
@@ -8061,7 +7827,7 @@ var BlockcastMmt = (() => {
8061
7827
  }
8062
7828
  });
8063
7829
  }
8064
- function validateCmsfMmtpCatalog(value, options = {}) {
7830
+ function validateCmsfMmtpCatalog(value, _options = {}) {
8065
7831
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
8066
7832
  catalogError("catalog_object", "$", "catalog must be an object");
8067
7833
  }
@@ -8252,8 +8018,6 @@ var BlockcastMmt = (() => {
8252
8018
  }
8253
8019
  }
8254
8020
  }
8255
- const [multicastIssue] = multicastCatalogIssues(catalog, options);
8256
- if (multicastIssue) catalogError(multicastIssue.code, multicastIssue.path, multicastIssue.message);
8257
8021
  validateArchive(catalog);
8258
8022
  return catalog;
8259
8023
  }
@@ -8356,9 +8120,10 @@ var BlockcastMmt = (() => {
8356
8120
  function heldLayerAnchorHorizon(retentionBlocks, k) {
8357
8121
  return retentionBlocks === void 0 || k <= 0 ? HORIZON_CAP : Math.min(HORIZON_CAP, retentionBlocks * k);
8358
8122
  }
8359
- var _layerNames, _states;
8123
+ var _layerNames, _states, _HeldLayerAdmission_instances, admit_fn;
8360
8124
  var HeldLayerAdmission = class {
8361
8125
  constructor(layers, heldLayers = [0]) {
8126
+ __privateAdd(this, _HeldLayerAdmission_instances);
8362
8127
  __privateAdd(this, _layerNames);
8363
8128
  __privateAdd(this, _states);
8364
8129
  const held = new Set(heldLayers);
@@ -8376,56 +8141,78 @@ var BlockcastMmt = (() => {
8376
8141
  * @returns undefined to admit, `'discard'`, or the fail-closed reason.
8377
8142
  */
8378
8143
  admit(repairTrackName, ssStart, rsbLength, horizon, sourceFrontier) {
8379
- if (repairTrackName !== void 0 && !__privateGet(this, _layerNames).has(repairTrackName)) return "discard";
8380
- const farAhead = sourceFrontier !== void 0 && serial32Precedes(sourceFrontier, ssStart) && serial32Distance(sourceFrontier, ssStart) > horizon;
8381
- for (const state of __privateGet(this, _states)) {
8382
- let code;
8383
- if (farAhead) {
8384
- if (rsbLength < state.total) {
8385
- if (state.layer > 0 && repairTrackName === state.repairTrackName) code = "below_own_track_total";
8386
- else if (state.anchor !== void 0 || state.settled) code = "below_anchored_total";
8387
- }
8388
- } else {
8389
- if (state.newest === void 0 || serial32Precedes(state.newest, ssStart)) state.newest = ssStart;
8390
- const stale = (s) => serial32Distance(s, state.newest) > horizon;
8391
- if (state.latestBelow !== void 0 && stale(state.latestBelow)) {
8392
- delete state.latestBelow;
8393
- delete state.latestBelowRsb;
8144
+ return __privateMethod(this, _HeldLayerAdmission_instances, admit_fn).call(this, repairTrackName, ssStart, rsbLength, horizon, sourceFrontier, true);
8145
+ }
8146
+ /**
8147
+ * The core-00 7.5 carve-out for a repair object about to be discarded
8148
+ * (RS_ID or scheme bound): whether its own RSB_length, below a held layer's
8149
+ * total on that layer's track or on an anchored block, fails the instance
8150
+ * closed under 7.3.2. Leaves the anchored state as it was: a discarded
8151
+ * object anchors nothing.
8152
+ */
8153
+ failsClosed(repairTrackName, ssStart, rsbLength, horizon, sourceFrontier) {
8154
+ const saved = __privateGet(this, _states).map((state) => ({ ...state }));
8155
+ try {
8156
+ const verdict = __privateMethod(this, _HeldLayerAdmission_instances, admit_fn).call(this, repairTrackName, ssStart, rsbLength, horizon, sourceFrontier, false);
8157
+ return typeof verdict === "object" ? verdict : void 0;
8158
+ } finally {
8159
+ saved.forEach((state, i) => {
8160
+ __privateGet(this, _states)[i] = state;
8161
+ });
8162
+ }
8163
+ }
8164
+ };
8165
+ _layerNames = new WeakMap();
8166
+ _states = new WeakMap();
8167
+ _HeldLayerAdmission_instances = new WeakSet();
8168
+ admit_fn = function(repairTrackName, ssStart, rsbLength, horizon, sourceFrontier, retroactive) {
8169
+ if (repairTrackName !== void 0 && !__privateGet(this, _layerNames).has(repairTrackName)) return "discard";
8170
+ const farAhead = sourceFrontier !== void 0 && serial32Precedes(sourceFrontier, ssStart) && serial32Distance(sourceFrontier, ssStart) > horizon;
8171
+ for (const state of __privateGet(this, _states)) {
8172
+ let code;
8173
+ if (farAhead) {
8174
+ if (rsbLength < state.total) {
8175
+ if (state.layer > 0 && repairTrackName === state.repairTrackName) code = "below_own_track_total";
8176
+ else if (state.anchor !== void 0 || state.settled) code = "below_anchored_total";
8177
+ }
8178
+ } else {
8179
+ if (state.newest === void 0 || serial32Precedes(state.newest, ssStart)) state.newest = ssStart;
8180
+ const stale = (s) => serial32Distance(s, state.newest) > horizon;
8181
+ if (state.latestBelow !== void 0 && stale(state.latestBelow)) {
8182
+ delete state.latestBelow;
8183
+ delete state.latestBelowRsb;
8184
+ }
8185
+ if (state.anchor !== void 0 && stale(state.anchor)) state.settled = true;
8186
+ if (rsbLength < state.total) {
8187
+ if (state.layer > 0 && repairTrackName === state.repairTrackName) code = "below_own_track_total";
8188
+ else if (stale(ssStart)) continue;
8189
+ else if (state.settled || state.anchor !== void 0 && !serial32Precedes(ssStart, state.anchor)) code = "below_anchored_total";
8190
+ else if (state.latestBelow === void 0 || serial32Precedes(state.latestBelow, ssStart)) {
8191
+ state.latestBelow = ssStart;
8192
+ state.latestBelowRsb = rsbLength;
8394
8193
  }
8395
- if (state.anchor !== void 0 && stale(state.anchor)) state.settled = true;
8396
- if (rsbLength < state.total) {
8397
- if (state.layer > 0 && repairTrackName === state.repairTrackName) code = "below_own_track_total";
8398
- else if (stale(ssStart)) continue;
8399
- else if (state.settled || state.anchor !== void 0 && !serial32Precedes(ssStart, state.anchor)) code = "below_anchored_total";
8400
- else if (state.latestBelow === void 0 || serial32Precedes(state.latestBelow, ssStart)) {
8401
- state.latestBelow = ssStart;
8402
- state.latestBelowRsb = rsbLength;
8403
- }
8404
- } else if (!state.settled && !stale(ssStart)) {
8405
- if (state.anchor === void 0 || serial32Precedes(ssStart, state.anchor)) state.anchor = ssStart;
8406
- if (state.latestBelow !== void 0 && !serial32Precedes(state.latestBelow, state.anchor)) {
8407
- code = "below_anchored_total";
8408
- ssStart = state.latestBelow;
8409
- rsbLength = state.latestBelowRsb;
8410
- }
8194
+ } else if (!state.settled && !stale(ssStart)) {
8195
+ if (state.anchor === void 0 || serial32Precedes(ssStart, state.anchor)) state.anchor = ssStart;
8196
+ if (retroactive && state.latestBelow !== void 0 && !serial32Precedes(state.latestBelow, state.anchor)) {
8197
+ code = "below_anchored_total";
8198
+ ssStart = state.latestBelow;
8199
+ rsbLength = state.latestBelowRsb;
8411
8200
  }
8412
8201
  }
8413
- if (code !== void 0) {
8414
- return {
8415
- code,
8416
- layer: state.layer,
8417
- total: state.total,
8418
- rsbLength,
8419
- ssStart,
8420
- ...state.anchor === void 0 ? {} : { anchor: state.anchor }
8421
- };
8422
- }
8423
8202
  }
8424
- return void 0;
8203
+ if (code !== void 0) {
8204
+ return {
8205
+ code,
8206
+ layer: state.layer,
8207
+ total: state.total,
8208
+ rsbLength,
8209
+ ssStart,
8210
+ ...state.anchor === void 0 ? {} : { anchor: state.anchor }
8211
+ };
8212
+ }
8425
8213
  }
8214
+ return void 0;
8426
8215
  };
8427
- _layerNames = new WeakMap();
8428
- _states = new WeakMap();
8429
8216
 
8430
8217
  // src/mmtp-fec-session.ts
8431
8218
  var PATH_BITS = { moq: 1, multicast: 2 };
@@ -8597,18 +8384,29 @@ var BlockcastMmt = (() => {
8597
8384
  } catch {
8598
8385
  return __privateMethod(this, _MmtpFecSession_instances, drop_fn2).call(this, "repair:malformed");
8599
8386
  }
8600
- const frame = parseMoqRepairFrameWithHeader(packet, header);
8601
- if (!frame) return __privateMethod(this, _MmtpFecSession_instances, drop_fn2).call(this, "repair:malformed");
8602
- if (__privateGet(this, _repairPacketId) === void 0) {
8603
- __privateSet(this, _repairPacketId, frame.packetId);
8604
- } else if (frame.packetId !== __privateGet(this, _repairPacketId)) {
8387
+ const admission = admitMmtpRepairObject(packet, header, this.symbolSize, this.algorithm);
8388
+ if (!admission) return __privateMethod(this, _MmtpFecSession_instances, drop_fn2).call(this, "repair:malformed");
8389
+ if (!("frame" in admission)) return __privateMethod(this, _MmtpFecSession_instances, drop_fn2).call(this, "repair:length");
8390
+ const frame = admission.frame;
8391
+ if (__privateGet(this, _repairPacketId) !== void 0 && frame.packetId !== __privateGet(this, _repairPacketId)) {
8605
8392
  return __privateMethod(this, _MmtpFecSession_instances, drop_fn2).call(this, "repair:foreign-packet-id");
8606
8393
  }
8607
- if (frame.symbolData.byteLength !== this.symbolSize) return __privateMethod(this, _MmtpFecSession_instances, drop_fn2).call(this, "repair:symbol-size");
8608
- if (frame.ssbLength > this.sourceSymbols) return __privateMethod(this, _MmtpFecSession_instances, drop_fn2).call(this, "repair:ssb-length");
8609
- if (this.algorithm === "reed-solomon" && frame.ssbLength + frame.rsbLength > 255) {
8610
- return __privateMethod(this, _MmtpFecSession_instances, drop_fn2).call(this, "repair:rs-block-length");
8394
+ if (admission.reject) {
8395
+ const verdict2 = __privateGet(this, _heldLayers).failsClosed(
8396
+ repairTrackName,
8397
+ frame.ssStart,
8398
+ frame.rsbLength,
8399
+ __privateGet(this, _anchorHorizon),
8400
+ __privateGet(this, _newestSource)
8401
+ );
8402
+ if (verdict2) {
8403
+ __privateGet(this, _stats3).failClosed = verdict2;
8404
+ return __privateMethod(this, _MmtpFecSession_instances, drop_fn2).call(this, "repair:fail-closed");
8405
+ }
8406
+ return __privateMethod(this, _MmtpFecSession_instances, drop_fn2).call(this, admission.reject === "repairRsId" ? "repair:rs-id" : "repair:scheme-bound");
8611
8407
  }
8408
+ __privateGet(this, _repairPacketId) ?? __privateSet(this, _repairPacketId, frame.packetId);
8409
+ if (frame.ssbLength > this.sourceSymbols) return __privateMethod(this, _MmtpFecSession_instances, drop_fn2).call(this, "repair:ssb-length");
8612
8410
  const verdict = __privateGet(this, _heldLayers).admit(
8613
8411
  repairTrackName,
8614
8412
  frame.ssStart,
@@ -8895,8 +8693,8 @@ var BlockcastMmt = (() => {
8895
8693
  }
8896
8694
  var KEYFRAME_FLAGS = 33554432;
8897
8695
  var DELTA_FLAGS = 16842752;
8898
- var UINT32_MAX2 = 4294967295;
8899
- var MAX_TRACK_ID = UINT32_MAX2 - 1;
8696
+ var UINT32_MAX = 4294967295;
8697
+ var MAX_TRACK_ID = UINT32_MAX - 1;
8900
8698
  var INT32_MIN = -2147483648;
8901
8699
  var INT32_MAX = 2147483647;
8902
8700
  function assertTrackId(trackId, label) {
@@ -8905,8 +8703,8 @@ var BlockcastMmt = (() => {
8905
8703
  }
8906
8704
  }
8907
8705
  function assertTimescale(timescale, label) {
8908
- if (!Number.isInteger(timescale) || timescale <= 0 || timescale > UINT32_MAX2) {
8909
- throw new RangeError(`${label} must be an integer in 1..${UINT32_MAX2}, got ${timescale}`);
8706
+ if (!Number.isInteger(timescale) || timescale <= 0 || timescale > UINT32_MAX) {
8707
+ throw new RangeError(`${label} must be an integer in 1..${UINT32_MAX}, got ${timescale}`);
8910
8708
  }
8911
8709
  }
8912
8710
  function assertCompositionTimeOffsets(samples) {
@@ -10237,6 +10035,11 @@ var BlockcastMmt = (() => {
10237
10035
  const [leftText, rightText = ""] = address.split("::");
10238
10036
  const left = leftText ? leftText.split(":") : [];
10239
10037
  const right = rightText ? rightText.split(":") : [];
10038
+ const tail = compressed ? right : left;
10039
+ if (tail.length > 0 && tail[tail.length - 1].includes(".")) {
10040
+ const v4 = parseIpv4(tail.pop());
10041
+ tail.push((v4[0] << 8 | v4[1]).toString(16), (v4[2] << 8 | v4[3]).toString(16));
10042
+ }
10240
10043
  if (left.some((group) => group.includes(".")) || right.some((group) => group.includes("."))) {
10241
10044
  throw new Error(`invalid IPv6 address ${address}`);
10242
10045
  }
@@ -10259,7 +10062,7 @@ var BlockcastMmt = (() => {
10259
10062
  return parseIpv4(address);
10260
10063
  }
10261
10064
  function endpointBytes(endpoint) {
10262
- if (!validPort(endpoint.port) || !validPort(endpoint.manifestPort) || !validU32(endpoint.streamId)) {
10065
+ if (!validPort(endpoint.port) || !validU32(endpoint.streamId)) {
10263
10066
  throw new Error("invalid BCPV2 endpoint");
10264
10067
  }
10265
10068
  const source = parseBcProvenanceV2Address(endpoint.sourceAddress);
@@ -10319,7 +10122,7 @@ var BlockcastMmt = (() => {
10319
10122
  offset += source.byteLength;
10320
10123
  prefix.set(group, offset);
10321
10124
  offset += group.byteLength;
10322
- prefix.set(u16(endpoint.manifestPort), offset);
10125
+ prefix.set(u16(endpoint.port), offset);
10323
10126
  const message = new Uint8Array(prefix.byteLength + record.body.byteLength);
10324
10127
  message.set(prefix);
10325
10128
  message.set(record.body, prefix.byteLength);
@@ -11874,10 +11677,12 @@ var BlockcastMmt = (() => {
11874
11677
  __publicField(this, "holdBytes", { multicast: 0, unicast: 0 });
11875
11678
  __publicField(this, "order", 0);
11876
11679
  __publicField(this, "buckets");
11877
- this.maxAuthDelayMs = cfg.maxAuthDelayMs ?? 200;
11878
- this.manifestTimerMs = cfg.manifestTimerMs ?? 20;
11879
- if (!(this.maxAuthDelayMs >= 50 && this.maxAuthDelayMs <= 1e3) || !(this.manifestTimerMs >= 1)) {
11880
- throw new Error("BCPV2 endpoint ignored: maxAuthDelayMs or manifestTimerMs out of range");
11680
+ const delay = cfg.maxAuthDelayMs;
11681
+ this.maxAuthDelayMs = typeof delay !== "number" || !Number.isFinite(delay) ? 200 : delay < 50 ? 50 : delay > 1e3 ? 1e3 : Number.isInteger(delay) ? delay : 200;
11682
+ const timer = cfg.manifestTimerMs;
11683
+ this.manifestTimerMs = typeof timer !== "number" || !Number.isFinite(timer) ? 20 : timer < 1 || Number.isInteger(timer) ? timer : 20;
11684
+ if (!(this.manifestTimerMs >= 1)) {
11685
+ throw new Error("BCPV2 endpoint ignored: manifestTimerMs below 1");
11881
11686
  }
11882
11687
  if (!(cfg.bandwidth > 0))
11883
11688
  throw new Error("BCPV2 endpoint ignored: protected endpoint without bandwidth");
@@ -11917,7 +11722,7 @@ var BlockcastMmt = (() => {
11917
11722
  return 0;
11918
11723
  return this.lead ?? BCPV2_LEAD_BOUND_MS;
11919
11724
  }
11920
- /** Step 1: a record datagram on `manifestPort`, or a record object of the manifest track. */
11725
+ /** Step 1: a record datagram on `port`, or a record object of the manifest track. */
11921
11726
  onRecord(bytes, path) {
11922
11727
  var _a;
11923
11728
  const now = this.now();
@@ -12243,7 +12048,7 @@ var BlockcastMmt = (() => {
12243
12048
  }
12244
12049
  /**
12245
12050
  * Retire verified entries below their arrival path's replay floor, keeping a mirrored entry
12246
- * while the freshness check would still pass its copy on `manifestPort`.
12051
+ * while the freshness check would still pass its copy on `port`.
12247
12052
  * ponytail: linear scan per record; index by (kid, epoch) if retained sets grow past a few thousand.
12248
12053
  */
12249
12054
  prune(now) {