@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.
- package/README.md +9 -7
- package/dist/browser-runtime.js +6931 -6685
- package/dist/browser-runtime.js.map +4 -4
- package/dist/carp-catalog.d.ts +7 -7
- package/dist/carp-catalog.d.ts.map +1 -1
- package/dist/carp-catalog.js +65 -230
- package/dist/carp-catalog.js.map +3 -3
- package/dist/catalog-fec-params.js.map +1 -1
- package/dist/catalog-network-source.js.map +1 -1
- package/dist/delivery-catalog.js.map +1 -1
- package/dist/delta-fallback-config.js.map +1 -1
- package/dist/fec-manager.d.ts +25 -1
- package/dist/fec-manager.d.ts.map +1 -1
- package/dist/fec-manager.js +208 -15
- package/dist/fec-manager.js.map +2 -2
- package/dist/fec-state.js.map +1 -1
- package/dist/fec-worker-setup.js.map +1 -1
- package/dist/held-layer-admission.d.ts +8 -0
- package/dist/held-layer-admission.d.ts.map +1 -1
- package/dist/held-layer-admission.js +22 -1
- package/dist/held-layer-admission.js.map +2 -2
- package/dist/iso-block-locator.js.map +1 -1
- package/dist/mfu-reassembler.js.map +1 -1
- package/dist/mmtp-container-parser.js.map +1 -1
- package/dist/mmtp-container-parser.no-modules.js +127 -322
- package/dist/mmtp-container-parser.no-modules.js.map +4 -4
- package/dist/mmtp-decode-pipeline.js.map +1 -1
- package/dist/mmtp-fec-session.d.ts.map +1 -1
- package/dist/mmtp-fec-session.js +61 -15
- package/dist/mmtp-fec-session.js.map +3 -3
- package/dist/mmtp-router.d.ts +11 -6
- package/dist/mmtp-router.d.ts.map +1 -1
- package/dist/mmtp-router.js +47 -10
- package/dist/mmtp-router.js.map +3 -3
- package/dist/multicast-auth-catalog.d.ts +8 -8
- package/dist/multicast-auth-catalog.d.ts.map +1 -1
- package/dist/multicast-auth-catalog.js +171 -75
- package/dist/multicast-auth-catalog.js.map +4 -4
- package/dist/multicast-input.d.ts +5 -5
- package/dist/multicast-input.d.ts.map +1 -1
- package/dist/multicast-input.js +126 -49
- package/dist/multicast-input.js.map +3 -3
- package/dist/multicast-manager.d.ts +11 -4
- package/dist/multicast-manager.d.ts.map +1 -1
- package/dist/multicast-manager.js +7922 -7700
- package/dist/multicast-manager.js.map +4 -4
- package/dist/multicast-transport.d.ts +8 -0
- package/dist/multicast-transport.d.ts.map +1 -1
- package/dist/multicast-transport.js +27 -3
- package/dist/multicast-transport.js.map +3 -3
- package/dist/multicast-unwrap.js +2 -2
- package/dist/multicast-unwrap.js.map +2 -2
- package/dist/no-modules.js +81 -278
- package/dist/no-modules.js.map +4 -4
- package/dist/repair-fetch.js.map +1 -1
- package/dist/signaling.js.map +1 -1
- package/dist/timestamp-codec.js.map +1 -1
- package/dist/wasm-mfu-reassembler.js.map +1 -1
- package/dist/wasm-mmtp-container-parser.js.map +1 -1
- package/package.json +5 -5
- package/schemas/catalog.schema.json +15 -7
- package/src/carp-catalog.ts +14 -13
- package/src/fec-manager.ts +89 -14
- package/src/held-layer-admission.ts +38 -1
- package/src/mmtp-fec-session.ts +20 -12
- package/src/mmtp-router.ts +65 -16
- package/src/multicast-auth-catalog.ts +43 -36
- package/src/multicast-input.ts +28 -8
- package/src/multicast-manager.ts +38 -34
- package/src/multicast-transport.ts +31 -3
- package/test-vectors/bc-provenance-v2.json +614 -175
- package/test-vectors/catalog/README.md +24 -8
- package/test-vectors/catalog/negative/multicast-auth-asm-missing-sender-address.json +0 -2
- package/test-vectors/catalog/negative/{multicast-auth-manifest-port-equals-port.json → multicast-auth-draft01-manifest-port.json} +1 -1
- package/test-vectors/catalog/negative/multicast-auth-duplicate-first-epoch.json +0 -1
- package/test-vectors/catalog/negative/multicast-auth-duplicate-kid.json +0 -1
- package/test-vectors/catalog/negative/multicast-auth-endpoint-auth-without-object-auth.json +0 -1
- package/test-vectors/catalog/negative/multicast-auth-endpoint-missing-bandwidth.json +0 -1
- package/test-vectors/catalog/negative/multicast-auth-manifest-timer-out-of-range.json +0 -1
- package/test-vectors/catalog/negative/multicast-auth-sender-address-on-ssm.json +0 -1
- package/test-vectors/catalog/negative/multicast-auth-shared-sg-port.json +73 -0
- package/test-vectors/catalog/negative/multicast-auth-version-1.json +0 -1
- package/test-vectors/catalog/negative/multicast-auth-version-3.json +0 -1
- package/test-vectors/catalog/negative/multicast-auth-version-missing.json +0 -1
- package/test-vectors/catalog/negative/multicast-endpoint-address-family-mismatch.json +55 -0
- package/test-vectors/catalog/negative/multicast-endpoint-address-host-name.json +55 -0
- package/test-vectors/catalog/negative/multicast-endpoint-address-leading-zero.json +55 -0
- package/test-vectors/catalog/negative/multicast-endpoint-address-ls32-leading-zero.json +55 -0
- package/test-vectors/catalog/negative/multicast-endpoint-address-mapped-family-mismatch.json +55 -0
- package/test-vectors/catalog/negative/multicast-endpoint-address-zone-id.json +55 -0
- package/test-vectors/catalog/positive/multicast-auth-asm-sender.json +0 -2
- 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
- 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
- package/test-vectors/catalog/positive/multicast-auth-max-auth-delay-string.json +55 -0
- package/test-vectors/catalog/positive/multicast-auth-rotation.json +0 -1
- package/test-vectors/catalog/positive/multicast-draft01-7-2-6-example.json +0 -1
- package/test-vectors/catalog/section-codes.json +8 -3
- package/test-vectors/catalog-multicast-auth-v1.json +0 -1
- package/test-vectors/catalog-validation.json +3 -3
- package/test-vectors/generate-bc-provenance-v2.mjs +94 -14
package/dist/no-modules.js
CHANGED
|
@@ -319,14 +319,11 @@ function readRepairFecPayloadIdFields(mmtpPacket, parsedHeader) {
|
|
|
319
319
|
dataOffset: off + REPAIR_FEC_PID_SIZE
|
|
320
320
|
};
|
|
321
321
|
}
|
|
322
|
-
function
|
|
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.
|
|
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,
|
|
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
|
|
8454
|
-
if (!
|
|
8455
|
-
if (
|
|
8456
|
-
|
|
8457
|
-
|
|
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 (
|
|
8461
|
-
|
|
8462
|
-
|
|
8463
|
-
|
|
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
|
|
8740
|
-
var MAX_TRACK_ID =
|
|
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 >
|
|
8750
|
-
throw new RangeError(`${label} must be an integer in 1..${
|
|
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) || !
|
|
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.
|
|
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
|
-
|
|
11701
|
-
this.
|
|
11702
|
-
|
|
11703
|
-
|
|
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 `
|
|
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 `
|
|
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) {
|