@blockcast/mmt-container 1.0.1 → 2.0.0-main.609e08d7e29f

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 (101) hide show
  1. package/README.md +9 -7
  2. package/dist/browser-runtime.js +6972 -6693
  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 +3 -0
  11. package/dist/delivery-catalog.js.map +2 -2
  12. package/dist/delta-fallback-config.js.map +1 -1
  13. package/dist/fec-manager.d.ts +25 -1
  14. package/dist/fec-manager.d.ts.map +1 -1
  15. package/dist/fec-manager.js +208 -15
  16. package/dist/fec-manager.js.map +2 -2
  17. package/dist/fec-state.js.map +1 -1
  18. package/dist/fec-worker-setup.js.map +1 -1
  19. package/dist/held-layer-admission.d.ts +8 -0
  20. package/dist/held-layer-admission.d.ts.map +1 -1
  21. package/dist/held-layer-admission.js +22 -1
  22. package/dist/held-layer-admission.js.map +2 -2
  23. package/dist/iso-block-locator.js.map +1 -1
  24. package/dist/mfu-reassembler.js.map +1 -1
  25. package/dist/mmtp-container-parser.js.map +1 -1
  26. package/dist/mmtp-container-parser.no-modules.js +146 -332
  27. package/dist/mmtp-container-parser.no-modules.js.map +4 -4
  28. package/dist/mmtp-decode-pipeline.js.map +1 -1
  29. package/dist/mmtp-fec-session.d.ts.map +1 -1
  30. package/dist/mmtp-fec-session.js +61 -15
  31. package/dist/mmtp-fec-session.js.map +3 -3
  32. package/dist/mmtp-router.d.ts +11 -6
  33. package/dist/mmtp-router.d.ts.map +1 -1
  34. package/dist/mmtp-router.js +47 -10
  35. package/dist/mmtp-router.js.map +3 -3
  36. package/dist/multicast-auth-catalog.d.ts +8 -8
  37. package/dist/multicast-auth-catalog.d.ts.map +1 -1
  38. package/dist/multicast-auth-catalog.js +171 -75
  39. package/dist/multicast-auth-catalog.js.map +4 -4
  40. package/dist/multicast-input.d.ts +5 -5
  41. package/dist/multicast-input.d.ts.map +1 -1
  42. package/dist/multicast-input.js +126 -49
  43. package/dist/multicast-input.js.map +3 -3
  44. package/dist/multicast-manager.d.ts +11 -4
  45. package/dist/multicast-manager.d.ts.map +1 -1
  46. package/dist/multicast-manager.js +7914 -7659
  47. package/dist/multicast-manager.js.map +4 -4
  48. package/dist/multicast-transport.d.ts +16 -1
  49. package/dist/multicast-transport.d.ts.map +1 -1
  50. package/dist/multicast-transport.js +120 -5
  51. package/dist/multicast-transport.js.map +4 -4
  52. package/dist/multicast-unwrap.js +2 -2
  53. package/dist/multicast-unwrap.js.map +2 -2
  54. package/dist/no-modules.js +100 -288
  55. package/dist/no-modules.js.map +4 -4
  56. package/dist/repair-fetch.js.map +1 -1
  57. package/dist/signaling.js.map +1 -1
  58. package/dist/timestamp-codec.js.map +1 -1
  59. package/dist/wasm-mfu-reassembler.js.map +1 -1
  60. package/dist/wasm-mmtp-container-parser.js.map +1 -1
  61. package/package.json +5 -5
  62. package/schemas/catalog.schema.json +15 -7
  63. package/src/carp-catalog.ts +14 -13
  64. package/src/fec-manager.ts +89 -14
  65. package/src/held-layer-admission.ts +38 -1
  66. package/src/mmtp-fec-session.ts +20 -12
  67. package/src/mmtp-router.ts +65 -16
  68. package/src/multicast-auth-catalog.ts +43 -36
  69. package/src/multicast-input.ts +28 -8
  70. package/src/multicast-manager.ts +38 -34
  71. package/src/multicast-transport.ts +74 -7
  72. package/test-vectors/bc-provenance-v2.json +614 -175
  73. package/test-vectors/catalog/README.md +24 -8
  74. package/test-vectors/catalog/negative/multicast-auth-asm-missing-sender-address.json +0 -2
  75. package/test-vectors/catalog/negative/{multicast-auth-manifest-port-equals-port.json → multicast-auth-draft01-manifest-port.json} +1 -1
  76. package/test-vectors/catalog/negative/multicast-auth-duplicate-first-epoch.json +0 -1
  77. package/test-vectors/catalog/negative/multicast-auth-duplicate-kid.json +0 -1
  78. package/test-vectors/catalog/negative/multicast-auth-endpoint-auth-without-object-auth.json +0 -1
  79. package/test-vectors/catalog/negative/multicast-auth-endpoint-missing-bandwidth.json +0 -1
  80. package/test-vectors/catalog/negative/multicast-auth-manifest-timer-out-of-range.json +0 -1
  81. package/test-vectors/catalog/negative/multicast-auth-sender-address-on-ssm.json +0 -1
  82. package/test-vectors/catalog/negative/multicast-auth-shared-sg-port.json +73 -0
  83. package/test-vectors/catalog/negative/multicast-auth-version-1.json +0 -1
  84. package/test-vectors/catalog/negative/multicast-auth-version-3.json +0 -1
  85. package/test-vectors/catalog/negative/multicast-auth-version-missing.json +0 -1
  86. package/test-vectors/catalog/negative/multicast-endpoint-address-family-mismatch.json +55 -0
  87. package/test-vectors/catalog/negative/multicast-endpoint-address-host-name.json +55 -0
  88. package/test-vectors/catalog/negative/multicast-endpoint-address-leading-zero.json +55 -0
  89. package/test-vectors/catalog/negative/multicast-endpoint-address-ls32-leading-zero.json +55 -0
  90. package/test-vectors/catalog/negative/multicast-endpoint-address-mapped-family-mismatch.json +55 -0
  91. package/test-vectors/catalog/negative/multicast-endpoint-address-zone-id.json +55 -0
  92. package/test-vectors/catalog/positive/multicast-auth-asm-sender.json +0 -2
  93. 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
  94. 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
  95. package/test-vectors/catalog/positive/multicast-auth-max-auth-delay-string.json +55 -0
  96. package/test-vectors/catalog/positive/multicast-auth-rotation.json +0 -1
  97. package/test-vectors/catalog/positive/multicast-draft01-7-2-6-example.json +0 -1
  98. package/test-vectors/catalog/section-codes.json +8 -3
  99. package/test-vectors/catalog-multicast-auth-v1.json +0 -1
  100. package/test-vectors/catalog-validation.json +3 -3
  101. 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);
@@ -11830,7 +11633,6 @@ var BlockcastMmt = (() => {
11830
11633
  var BCPV2_LEAD_BOUND_MS = 5e3;
11831
11634
  var REPLAY_WINDOW = 64n;
11832
11635
  var BUCKET_DEPTH = 64;
11833
- var BUCKET_RATE_PER_MS = 1;
11834
11636
  var hex = (bytes) => Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
11835
11637
  function sameBytes(a, b) {
11836
11638
  if (a.byteLength !== b.byteLength)
@@ -11860,6 +11662,10 @@ var BlockcastMmt = (() => {
11860
11662
  __publicField(this, "verifySig");
11861
11663
  __publicField(this, "accepted");
11862
11664
  __publicField(this, "queueLimit");
11665
+ /** Octets a held datagram is charged beyond its payload: the IPv4 or IPv6 plus UDP headers (step 2). */
11666
+ __publicField(this, "headerOctets");
11667
+ /** Budget refill, tokens per ms: V / 1000 (step 1). */
11668
+ __publicField(this, "bucketRatePerMs");
11863
11669
  __publicField(this, "current", null);
11864
11670
  __publicField(this, "previous", null);
11865
11671
  __publicField(this, "graceStart", {});
@@ -11874,10 +11680,12 @@ var BlockcastMmt = (() => {
11874
11680
  __publicField(this, "holdBytes", { multicast: 0, unicast: 0 });
11875
11681
  __publicField(this, "order", 0);
11876
11682
  __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");
11683
+ const delay = cfg.maxAuthDelayMs;
11684
+ this.maxAuthDelayMs = typeof delay !== "number" || !Number.isFinite(delay) ? 200 : delay < 50 ? 50 : delay > 1e3 ? 1e3 : Number.isInteger(delay) ? delay : 200;
11685
+ const timer = cfg.manifestTimerMs;
11686
+ this.manifestTimerMs = typeof timer !== "number" || !Number.isFinite(timer) ? 20 : timer < 1 || Number.isInteger(timer) ? timer : 20;
11687
+ if (!(this.manifestTimerMs >= 1)) {
11688
+ throw new Error("BCPV2 endpoint ignored: manifestTimerMs below 1");
11881
11689
  }
11882
11690
  if (!(cfg.bandwidth > 0))
11883
11691
  throw new Error("BCPV2 endpoint ignored: protected endpoint without bandwidth");
@@ -11902,6 +11710,8 @@ var BlockcastMmt = (() => {
11902
11710
  multicast: this.maxAuthDelayMs * bandwidth / 8e3,
11903
11711
  unicast: (this.maxAuthDelayMs + this.widenMs) * bandwidth / 8e3
11904
11712
  };
11713
+ this.headerOctets = parseBcProvenanceV2Address(cfg.endpoint.groupAddress).byteLength === 16 ? 48 : 28;
11714
+ this.bucketRatePerMs = Math.max(1e3, 1.25 * bandwidth / (8 * 512 * 37)) / 1e3;
11905
11715
  const t = this.now();
11906
11716
  this.buckets = {
11907
11717
  multicast: { tokens: BUCKET_DEPTH, last: t, dropped: 0 },
@@ -11917,7 +11727,7 @@ var BlockcastMmt = (() => {
11917
11727
  return 0;
11918
11728
  return this.lead ?? BCPV2_LEAD_BOUND_MS;
11919
11729
  }
11920
- /** Step 1: a record datagram on `manifestPort`, or a record object of the manifest track. */
11730
+ /** Step 1: a record datagram on `port`, or a record object of the manifest track. */
11921
11731
  onRecord(bytes, path) {
11922
11732
  var _a;
11923
11733
  const now = this.now();
@@ -12001,7 +11811,7 @@ var BlockcastMmt = (() => {
12001
11811
  arrival: now,
12002
11812
  sent,
12003
11813
  digest: hex(sha2562(bytes)),
12004
- pairs: record.digests.map((d, i) => ({ digest: hex(d), seq: record.firstSeq + BigInt(i), admitted: false })),
11814
+ pairs: record.digests.map((d, i) => ({ digest: hex(d), epoch, seq: record.firstSeq + BigInt(i), admitted: false })),
12005
11815
  awaitingMulticast: path === "mirror"
12006
11816
  };
12007
11817
  this.verified.set(id, entry);
@@ -12068,12 +11878,12 @@ var BlockcastMmt = (() => {
12068
11878
  digest(datagram) {
12069
11879
  return hex(bcProvenanceV2DatagramDigest(this.cfg.endpoint, datagram));
12070
11880
  }
12071
- /** Consume the lowest-seq pending entry for `digest`, admitting it. */
11881
+ /** Consume the pending entry for `digest` lowest in (epoch, sequence) order, admitting it. */
12072
11882
  consumePending(digest, now) {
12073
11883
  const list = this.pending.get(digest);
12074
11884
  if (!list?.length)
12075
11885
  return false;
12076
- list.sort((a, b) => a.pair.seq < b.pair.seq ? -1 : a.pair.seq > b.pair.seq ? 1 : 0);
11886
+ list.sort((a, b) => a.pair.epoch - b.pair.epoch || (a.pair.seq < b.pair.seq ? -1 : a.pair.seq > b.pair.seq ? 1 : 0));
12077
11887
  const hit = list.shift();
12078
11888
  if (list.length === 0)
12079
11889
  this.pending.delete(digest);
@@ -12082,14 +11892,18 @@ var BlockcastMmt = (() => {
12082
11892
  this.admittedUntil.set(digest, now + this.maxAuthDelayMs + this.leadMs);
12083
11893
  return true;
12084
11894
  }
11895
+ /** A held datagram's charge against its queue: payload plus IP and UDP headers. */
11896
+ charge(held) {
11897
+ return held.datagram.byteLength + this.headerOctets;
11898
+ }
12085
11899
  enqueue(held, path, now) {
12086
- if (this.holdBytes[path] + held.datagram.byteLength > this.queueLimit[path]) {
11900
+ if (this.holdBytes[path] + this.charge(held) > this.queueLimit[path]) {
12087
11901
  return { verdict: "overflow", admitted: [] };
12088
11902
  }
12089
11903
  held.expiry = now + this.maxAuthDelayMs + (path === "unicast" ? this.widenMs : 0);
12090
11904
  held.order = this.order++;
12091
11905
  this.hold[path].push(held);
12092
- this.holdBytes[path] += held.datagram.byteLength;
11906
+ this.holdBytes[path] += this.charge(held);
12093
11907
  return { verdict: "held", admitted: [] };
12094
11908
  }
12095
11909
  /** Remove and return the oldest held datagram with `digest`, across both queues. */
@@ -12104,7 +11918,7 @@ var BlockcastMmt = (() => {
12104
11918
  if (!best)
12105
11919
  return void 0;
12106
11920
  const [held] = this.hold[best.path].splice(best.index, 1);
12107
- this.holdBytes[best.path] -= held.datagram.byteLength;
11921
+ this.holdBytes[best.path] -= this.charge(held);
12108
11922
  return held.datagram;
12109
11923
  }
12110
11924
  dropAdmittedHeld() {
@@ -12112,7 +11926,7 @@ var BlockcastMmt = (() => {
12112
11926
  this.hold[path] = this.hold[path].filter((h) => {
12113
11927
  const keep = h.digest === void 0 || !this.admittedUntil.has(h.digest);
12114
11928
  if (!keep)
12115
- this.holdBytes[path] -= h.datagram.byteLength;
11929
+ this.holdBytes[path] -= this.charge(h);
12116
11930
  return keep;
12117
11931
  });
12118
11932
  }
@@ -12197,7 +12011,7 @@ var BlockcastMmt = (() => {
12197
12011
  }
12198
12012
  take(path, now) {
12199
12013
  const b = this.buckets[path];
12200
- b.tokens = Math.min(BUCKET_DEPTH, b.tokens + (now - b.last) * BUCKET_RATE_PER_MS);
12014
+ b.tokens = Math.min(BUCKET_DEPTH, b.tokens + (now - b.last) * this.bucketRatePerMs);
12201
12015
  b.last = now;
12202
12016
  if (b.tokens < 1) {
12203
12017
  b.dropped++;
@@ -12211,7 +12025,7 @@ var BlockcastMmt = (() => {
12211
12025
  this.hold[path] = this.hold[path].filter((h) => {
12212
12026
  if (h.expiry > now)
12213
12027
  return true;
12214
- this.holdBytes[path] -= h.datagram.byteLength;
12028
+ this.holdBytes[path] -= this.charge(h);
12215
12029
  this.expired[path]++;
12216
12030
  return false;
12217
12031
  });
@@ -12243,7 +12057,7 @@ var BlockcastMmt = (() => {
12243
12057
  }
12244
12058
  /**
12245
12059
  * 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`.
12060
+ * while the freshness check would still pass its copy on `port`.
12247
12061
  * ponytail: linear scan per record; index by (kid, epoch) if retained sets grow past a few thousand.
12248
12062
  */
12249
12063
  prune(now) {