@blockcast/mmt-container 0.4.1 → 0.5.0-main.34c7b9c2cfa0
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 +2 -2
- package/dist/browser-runtime.js +3769 -364
- package/dist/browser-runtime.js.map +4 -4
- package/dist/cmcd-producer.d.ts +5 -1
- package/dist/cmcd-producer.d.ts.map +1 -1
- package/dist/cmcd-producer.js.map +2 -2
- package/dist/delivery-catalog.js +18 -2
- package/dist/delivery-catalog.js.map +2 -2
- package/dist/fec-manager.d.ts +21 -16
- package/dist/fec-manager.d.ts.map +1 -1
- package/dist/fec-manager.js +197 -80
- package/dist/fec-manager.js.map +3 -3
- package/dist/held-layer-admission.d.ts +58 -0
- package/dist/held-layer-admission.d.ts.map +1 -0
- package/dist/held-layer-admission.js +105 -0
- package/dist/held-layer-admission.js.map +7 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +2 -2
- package/dist/mmtp-container-parser.d.ts +10 -0
- package/dist/mmtp-container-parser.d.ts.map +1 -1
- package/dist/mmtp-container-parser.js +31 -1
- package/dist/mmtp-container-parser.js.map +2 -2
- package/dist/mmtp-container-parser.no-modules.js +2713 -1024
- package/dist/mmtp-container-parser.no-modules.js.map +4 -4
- package/dist/mmtp-decode-pipeline.js +31 -1
- package/dist/mmtp-decode-pipeline.js.map +2 -2
- package/dist/mmtp-fec-session.d.ts +36 -3
- package/dist/mmtp-fec-session.d.ts.map +1 -1
- package/dist/mmtp-fec-session.js +114 -5
- package/dist/mmtp-fec-session.js.map +3 -3
- package/dist/mmtp-router.d.ts +96 -29
- package/dist/mmtp-router.d.ts.map +1 -1
- package/dist/mmtp-router.js +522 -205
- package/dist/mmtp-router.js.map +2 -2
- package/dist/multicast-auth-catalog.d.ts +10 -2
- package/dist/multicast-auth-catalog.d.ts.map +1 -1
- package/dist/multicast-auth-catalog.js +422 -22
- package/dist/multicast-auth-catalog.js.map +4 -4
- package/dist/multicast-manager.d.ts +78 -11
- package/dist/multicast-manager.d.ts.map +1 -1
- package/dist/multicast-manager.js +3663 -333
- package/dist/multicast-manager.js.map +4 -4
- package/dist/multicast-transport.d.ts +4 -1
- package/dist/multicast-transport.d.ts.map +1 -1
- package/dist/multicast-transport.js +20 -3
- package/dist/multicast-transport.js.map +2 -2
- package/dist/no-modules.d.ts +1 -2
- package/dist/no-modules.d.ts.map +1 -1
- package/dist/no-modules.js +2699 -1008
- package/dist/no-modules.js.map +4 -4
- package/dist/repair-fetch.d.ts +50 -10
- package/dist/repair-fetch.d.ts.map +1 -1
- package/dist/repair-fetch.js +62 -20
- package/dist/repair-fetch.js.map +2 -2
- package/dist/wasm-mmtp-container-parser.js +31 -1
- package/dist/wasm-mmtp-container-parser.js.map +2 -2
- package/package.json +7 -15
- package/src/cmcd-producer.ts +6 -1
- package/src/fec-manager.ts +147 -131
- package/src/held-layer-admission.ts +161 -0
- package/src/index.ts +6 -9
- package/src/mmtp-container-parser.ts +48 -0
- package/src/mmtp-fec-session.ts +76 -3
- package/src/mmtp-router.ts +637 -274
- package/src/multicast-auth-catalog.ts +81 -8
- package/src/multicast-manager.ts +274 -49
- package/src/multicast-transport.ts +6 -2
- package/src/no-modules.ts +1 -6
- package/src/repair-fetch.ts +154 -34
- package/test-vectors/catalog/negative/multicast-auth-padded-key.json +0 -1
- package/test-vectors/catalog/negative/multicast-auth-reused-track-format.json +0 -1
- package/test-vectors/catalog/negative/multicast-auth-root-dotted-key.json +0 -1
- package/test-vectors/catalog/negative/multicast-auth-trailing-pad-bits.json +0 -1
- package/test-vectors/repair-policy-v1.json +113 -0
- package/dist/manifest-auth-plan.d.ts +0 -31
- package/dist/manifest-auth-plan.d.ts.map +0 -1
- package/dist/manifest-auth-plan.js +0 -426
- package/dist/manifest-auth-plan.js.map +0 -7
- package/dist/source-auth.d.ts +0 -2
- package/dist/source-auth.d.ts.map +0 -1
- package/dist/source-auth.js +0 -3912
- package/dist/source-auth.js.map +0 -7
- package/src/manifest-auth-plan.ts +0 -55
- package/src/source-auth.ts +0 -1
- package/test-vectors/generate-source-auth-fixture.mjs +0 -114
- package/test-vectors/source-auth-v1.json +0 -61
|
@@ -1361,6 +1361,79 @@ function decideGap(input) {
|
|
|
1361
1361
|
});
|
|
1362
1362
|
}
|
|
1363
1363
|
|
|
1364
|
+
// src/held-layer-admission.ts
|
|
1365
|
+
var HORIZON_CAP = 1073741824;
|
|
1366
|
+
function heldLayerAnchorHorizon(retentionBlocks, k) {
|
|
1367
|
+
return retentionBlocks === void 0 || k <= 0 ? HORIZON_CAP : Math.min(HORIZON_CAP, retentionBlocks * k);
|
|
1368
|
+
}
|
|
1369
|
+
var HeldLayerAdmission = class {
|
|
1370
|
+
#layerNames;
|
|
1371
|
+
#states;
|
|
1372
|
+
constructor(layers, heldLayers = [0]) {
|
|
1373
|
+
const held = new Set(heldLayers);
|
|
1374
|
+
this.#layerNames = new Set(layers.map((l) => l.repairTrackName));
|
|
1375
|
+
this.#states = layers.filter((l) => held.has(l.layer)).map((l) => ({ layer: l.layer, repairTrackName: l.repairTrackName, total: l.rsIdOffset + l.repairSymbols }));
|
|
1376
|
+
}
|
|
1377
|
+
/**
|
|
1378
|
+
* Judges one repair packet, after the 4.5 discards and the size check and
|
|
1379
|
+
* before it places a block. `repairTrackName` is the subscribed repair
|
|
1380
|
+
* track, never inferred from RS_ID; without it only (b) runs. A packet on a
|
|
1381
|
+
* repair track the catalog does not list is `'discard'` and anchors nothing.
|
|
1382
|
+
* `sourceFrontier` is the newest source SS_ID, if any: a repair more than
|
|
1383
|
+
* `horizon` past it is judged without moving the anchored state.
|
|
1384
|
+
*
|
|
1385
|
+
* @returns undefined to admit, `'discard'`, or the fail-closed reason.
|
|
1386
|
+
*/
|
|
1387
|
+
admit(repairTrackName, ssStart, rsbLength, horizon, sourceFrontier) {
|
|
1388
|
+
if (repairTrackName !== void 0 && !this.#layerNames.has(repairTrackName)) return "discard";
|
|
1389
|
+
const farAhead = sourceFrontier !== void 0 && serial32Precedes(sourceFrontier, ssStart) && serial32Distance(sourceFrontier, ssStart) > horizon;
|
|
1390
|
+
for (const state of this.#states) {
|
|
1391
|
+
let code;
|
|
1392
|
+
if (farAhead) {
|
|
1393
|
+
if (rsbLength < state.total) {
|
|
1394
|
+
if (state.layer > 0 && repairTrackName === state.repairTrackName) code = "below_own_track_total";
|
|
1395
|
+
else if (state.anchor !== void 0 || state.settled) code = "below_anchored_total";
|
|
1396
|
+
}
|
|
1397
|
+
} else {
|
|
1398
|
+
if (state.newest === void 0 || serial32Precedes(state.newest, ssStart)) state.newest = ssStart;
|
|
1399
|
+
const stale = (s) => serial32Distance(s, state.newest) > horizon;
|
|
1400
|
+
if (state.latestBelow !== void 0 && stale(state.latestBelow)) {
|
|
1401
|
+
delete state.latestBelow;
|
|
1402
|
+
delete state.latestBelowRsb;
|
|
1403
|
+
}
|
|
1404
|
+
if (state.anchor !== void 0 && stale(state.anchor)) state.settled = true;
|
|
1405
|
+
if (rsbLength < state.total) {
|
|
1406
|
+
if (state.layer > 0 && repairTrackName === state.repairTrackName) code = "below_own_track_total";
|
|
1407
|
+
else if (stale(ssStart)) continue;
|
|
1408
|
+
else if (state.settled || state.anchor !== void 0 && !serial32Precedes(ssStart, state.anchor)) code = "below_anchored_total";
|
|
1409
|
+
else if (state.latestBelow === void 0 || serial32Precedes(state.latestBelow, ssStart)) {
|
|
1410
|
+
state.latestBelow = ssStart;
|
|
1411
|
+
state.latestBelowRsb = rsbLength;
|
|
1412
|
+
}
|
|
1413
|
+
} else if (!state.settled && !stale(ssStart)) {
|
|
1414
|
+
if (state.anchor === void 0 || serial32Precedes(ssStart, state.anchor)) state.anchor = ssStart;
|
|
1415
|
+
if (state.latestBelow !== void 0 && !serial32Precedes(state.latestBelow, state.anchor)) {
|
|
1416
|
+
code = "below_anchored_total";
|
|
1417
|
+
ssStart = state.latestBelow;
|
|
1418
|
+
rsbLength = state.latestBelowRsb;
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
if (code !== void 0) {
|
|
1423
|
+
return {
|
|
1424
|
+
code,
|
|
1425
|
+
layer: state.layer,
|
|
1426
|
+
total: state.total,
|
|
1427
|
+
rsbLength,
|
|
1428
|
+
ssStart,
|
|
1429
|
+
...state.anchor === void 0 ? {} : { anchor: state.anchor }
|
|
1430
|
+
};
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
return void 0;
|
|
1434
|
+
}
|
|
1435
|
+
};
|
|
1436
|
+
|
|
1364
1437
|
// src/fec-manager.ts
|
|
1365
1438
|
var RS_MAX_BLOCK_SYMBOLS = 255;
|
|
1366
1439
|
var WORKER_ERROR_LOG_CAP = 5;
|
|
@@ -1517,6 +1590,9 @@ var FECManager = class _FECManager {
|
|
|
1517
1590
|
// Receiver-local first-accepted-symbol time per block, bounded like
|
|
1518
1591
|
// #blockPathCounts (see blockAnchorAtMs).
|
|
1519
1592
|
this.#blockAnchorsMs = /* @__PURE__ */ new Map();
|
|
1593
|
+
// The receipt time of the packet being fed, when it is not now: a datagram
|
|
1594
|
+
// held on a miss and admitted later (withReceiptTime).
|
|
1595
|
+
this.#receiptAtMs = void 0;
|
|
1520
1596
|
// Exact catalog identity observed on accepted symbols for each FEC block.
|
|
1521
1597
|
// Conflicting identities collapse to null so recovery fails closed.
|
|
1522
1598
|
this.#blockCatalogTrackNames = /* @__PURE__ */ new Map();
|
|
@@ -1564,6 +1640,14 @@ var FECManager = class _FECManager {
|
|
|
1564
1640
|
// Worker recovery emits all K source symbols. Track recovery once per block
|
|
1565
1641
|
// while routing only ESI slots that were absent from the direct source path.
|
|
1566
1642
|
this.#workerRecoveredBlocks = /* @__PURE__ */ new Set();
|
|
1643
|
+
// Worker-recovered blocks a recovered packet of which admission refused,
|
|
1644
|
+
// until the decode's final symbol (7.2.7: the block is unrecovered).
|
|
1645
|
+
this.#refusedRecoveries = /* @__PURE__ */ new Set();
|
|
1646
|
+
// Worker-recovered blocks: the next ESI the decode's ordered emit owes. The
|
|
1647
|
+
// Worker emits every ESI 0..SSB_length-1 in order and drops only a symbol it
|
|
1648
|
+
// refuses (a malformed ssbg_mode1 symbol), so a skipped ESI is a dropped
|
|
1649
|
+
// recovered packet and the decode recovered nothing.
|
|
1650
|
+
this.#workerRecoveredNextEsi = /* @__PURE__ */ new Map();
|
|
1567
1651
|
// How the multicast leg delivers symbols (see MulticastSymbolDelivery). The
|
|
1568
1652
|
// provider declares it per transport join; absence keeps datagram semantics.
|
|
1569
1653
|
this.#multicastSymbolDelivery = "datagram";
|
|
@@ -1702,6 +1786,11 @@ var FECManager = class _FECManager {
|
|
|
1702
1786
|
/** A stream's SBN went backwards (or jumped implausibly): the source restarted. */
|
|
1703
1787
|
this.onSourceDiscontinuity = null;
|
|
1704
1788
|
// Worker-path callbacks — async recovery and stats from FecWorkerClient.
|
|
1789
|
+
/**
|
|
1790
|
+
* A recovered source symbol. Return false when admission refused the
|
|
1791
|
+
* packet it carries (bc-provenance v2, draft-ramadan-moq-multicast-01
|
|
1792
|
+
* 7.2.7): the block is then unrecovered and stays open to its FEC deadline.
|
|
1793
|
+
*/
|
|
1705
1794
|
this.onBlockRecovered = null;
|
|
1706
1795
|
this.onFecStatsUpdate = null;
|
|
1707
1796
|
}
|
|
@@ -1754,6 +1843,7 @@ var FECManager = class _FECManager {
|
|
|
1754
1843
|
#seenSymbols;
|
|
1755
1844
|
#blockPathCounts;
|
|
1756
1845
|
#blockAnchorsMs;
|
|
1846
|
+
#receiptAtMs;
|
|
1757
1847
|
#blockCatalogTrackNames;
|
|
1758
1848
|
#startupAlignedPackets;
|
|
1759
1849
|
#startupPartialBlocks;
|
|
@@ -1766,6 +1856,8 @@ var FECManager = class _FECManager {
|
|
|
1766
1856
|
#repairDeadlines;
|
|
1767
1857
|
#repairDeadlineAtMs;
|
|
1768
1858
|
#workerRecoveredBlocks;
|
|
1859
|
+
#refusedRecoveries;
|
|
1860
|
+
#workerRecoveredNextEsi;
|
|
1769
1861
|
#multicastSymbolDelivery;
|
|
1770
1862
|
#orderedFrontiers;
|
|
1771
1863
|
#orderedPendingBlocks;
|
|
@@ -1875,12 +1967,12 @@ var FECManager = class _FECManager {
|
|
|
1875
1967
|
* still be held behind it. Retirement is swept lazily here, on the stream's
|
|
1876
1968
|
* own terminal events, so it needs no timer.
|
|
1877
1969
|
*/
|
|
1878
|
-
#markTerminal(key, sbn, packetId,
|
|
1970
|
+
#markTerminal(key, sbn, packetId, clean2) {
|
|
1879
1971
|
const nowMs = performance.now();
|
|
1880
1972
|
this.#retireTerminalBlocks(packetId, nowMs);
|
|
1881
1973
|
this.#forgetOrderedPending(key);
|
|
1882
1974
|
this.#completedBlocks.add(key);
|
|
1883
|
-
if (
|
|
1975
|
+
if (clean2) this.#cleanlyCompletedBlocks.add(key);
|
|
1884
1976
|
this.#indexBlockKey(key, sbn, packetId);
|
|
1885
1977
|
const config = this.#trackConfig(packetId);
|
|
1886
1978
|
const expiresAtMs = nowMs + config.interleaveDepthMs + 2 * this.#blockLifetimeMs(config);
|
|
@@ -1905,6 +1997,8 @@ var FECManager = class _FECManager {
|
|
|
1905
1997
|
this.#completedBlocks.delete(record.key);
|
|
1906
1998
|
this.#cleanlyCompletedBlocks.delete(record.key);
|
|
1907
1999
|
this.#workerRecoveredBlocks.delete(record.key);
|
|
2000
|
+
this.#refusedRecoveries.delete(record.key);
|
|
2001
|
+
this.#workerRecoveredNextEsi.delete(record.key);
|
|
1908
2002
|
this.#failedBlocks.delete(record.key);
|
|
1909
2003
|
this.#resolvedBlockClasses.delete(record.key);
|
|
1910
2004
|
this.#repairedBlocks.delete(record.key);
|
|
@@ -2243,7 +2337,7 @@ var FECManager = class _FECManager {
|
|
|
2243
2337
|
* remainder, so repeated source progress costs no timer operation.
|
|
2244
2338
|
*/
|
|
2245
2339
|
#armRepairDeadline(key, sbn, packetId, catalogTrackName, delayMs) {
|
|
2246
|
-
const deadlineMs =
|
|
2340
|
+
const deadlineMs = this.#notEarlierUnderReceipt(this.#receiptNow() + delayMs, this.#repairDeadlineAtMs.get(key));
|
|
2247
2341
|
this.#repairDeadlineAtMs.set(key, deadlineMs);
|
|
2248
2342
|
const armed = this.#repairDeadlines.get(key);
|
|
2249
2343
|
if (armed?.handle != null) {
|
|
@@ -2263,7 +2357,7 @@ var FECManager = class _FECManager {
|
|
|
2263
2357
|
timer.handle = null;
|
|
2264
2358
|
this.#requestRepair(sbn, packetId, catalogTrackName);
|
|
2265
2359
|
};
|
|
2266
|
-
timer.handle = setTimeout(onFire,
|
|
2360
|
+
timer.handle = setTimeout(onFire, Math.max(0, deadlineMs - performance.now()));
|
|
2267
2361
|
this.#repairDeadlines.set(key, timer);
|
|
2268
2362
|
}
|
|
2269
2363
|
#requestRepair(sbn, packetId, catalogTrackName, settledByOrder = false) {
|
|
@@ -2667,7 +2761,10 @@ var FECManager = class _FECManager {
|
|
|
2667
2761
|
}
|
|
2668
2762
|
#armBlockTimeout(sbn, packetId, catalogTrackName, delayMs) {
|
|
2669
2763
|
const key = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
2670
|
-
const waitAtMs =
|
|
2764
|
+
const waitAtMs = this.#notEarlierUnderReceipt(
|
|
2765
|
+
this.#receiptNow() + Math.max(1, Math.ceil(delayMs)),
|
|
2766
|
+
this.#blockWaitDeadlinesMs.get(key)
|
|
2767
|
+
);
|
|
2671
2768
|
const holdAtMs = this.#repairHoldUntilMs.get(key);
|
|
2672
2769
|
this.#blockWaitDeadlinesMs.set(key, holdAtMs === void 0 ? waitAtMs : Math.min(waitAtMs, holdAtMs));
|
|
2673
2770
|
this.#applyBlockDeadline(key, sbn, packetId, catalogTrackName);
|
|
@@ -3056,6 +3153,8 @@ var FECManager = class _FECManager {
|
|
|
3056
3153
|
resetSet(this.#cleanlyCompletedBlocks);
|
|
3057
3154
|
resetSet(this.#repairRequestedBlocks);
|
|
3058
3155
|
resetSet(this.#workerRecoveredBlocks);
|
|
3156
|
+
resetSet(this.#refusedRecoveries);
|
|
3157
|
+
resetMap(this.#workerRecoveredNextEsi);
|
|
3059
3158
|
resetSet(this.#startupAlignedPackets);
|
|
3060
3159
|
resetSet(this.#startupPartialBlocks);
|
|
3061
3160
|
resetSet(this.#startupRepairReadyPackets);
|
|
@@ -3182,6 +3281,8 @@ var FECManager = class _FECManager {
|
|
|
3182
3281
|
dropSet(this.#cleanlyCompletedBlocks);
|
|
3183
3282
|
dropSet(this.#repairRequestedBlocks);
|
|
3184
3283
|
dropSet(this.#workerRecoveredBlocks);
|
|
3284
|
+
dropSet(this.#refusedRecoveries);
|
|
3285
|
+
dropMap(this.#workerRecoveredNextEsi);
|
|
3185
3286
|
dropSet(this.#startupAlignedPackets);
|
|
3186
3287
|
dropSet(this.#startupPartialBlocks);
|
|
3187
3288
|
dropSet(this.#startupRepairReadyPackets);
|
|
@@ -3295,7 +3396,9 @@ var FECManager = class _FECManager {
|
|
|
3295
3396
|
moqSymbols: 0,
|
|
3296
3397
|
complete: false
|
|
3297
3398
|
};
|
|
3298
|
-
|
|
3399
|
+
const receivedAtMs = this.#receiptNow();
|
|
3400
|
+
const anchorMs = this.#blockAnchorsMs.get(blockKey);
|
|
3401
|
+
if (anchorMs === void 0 || receivedAtMs < anchorMs) this.#blockAnchorsMs.set(blockKey, receivedAtMs);
|
|
3299
3402
|
if (source === "multicast") counts.multicastSymbols++;
|
|
3300
3403
|
else counts.moqSymbols++;
|
|
3301
3404
|
this.#blockPathCounts.set(blockKey, counts);
|
|
@@ -3456,6 +3559,18 @@ var FECManager = class _FECManager {
|
|
|
3456
3559
|
blockAnchorAtMs(sbn, packetId, catalogTrackName) {
|
|
3457
3560
|
return this.#blockAnchorsMs.get(this.#blockKey(sbn, packetId, catalogTrackName));
|
|
3458
3561
|
}
|
|
3562
|
+
/**
|
|
3563
|
+
* How long ago (ms) the oldest open block of the stream, one with an FEC
|
|
3564
|
+
* deadline, took its first accepted symbol; undefined with none open.
|
|
3565
|
+
*/
|
|
3566
|
+
oldestOpenBlockAgeMs(packetId, catalogTrackName) {
|
|
3567
|
+
let oldest = Infinity;
|
|
3568
|
+
for (const key of this.#activeBlocks.keys()) {
|
|
3569
|
+
if (!this.#belongsToStream(key, packetId, catalogTrackName)) continue;
|
|
3570
|
+
oldest = Math.min(oldest, this.#blockAnchorsMs.get(key) ?? Infinity);
|
|
3571
|
+
}
|
|
3572
|
+
return oldest === Infinity ? void 0 : performance.now() - oldest;
|
|
3573
|
+
}
|
|
3459
3574
|
/** Absolute fail deadline of a still-open block, or undefined. */
|
|
3460
3575
|
blockDeadlineAtMs(sbn, packetId, catalogTrackName) {
|
|
3461
3576
|
return this.#activeBlockDeadlinesMs.get(this.#blockKey(sbn, packetId, catalogTrackName));
|
|
@@ -3955,8 +4070,36 @@ var FECManager = class _FECManager {
|
|
|
3955
4070
|
if (!this.#claimDecoderBlock(client, meta.sbn, recoveredCatalogTrackName)) return;
|
|
3956
4071
|
const blockKey = this.#blockKey(meta.sbn, packetId, recoveredCatalogTrackName);
|
|
3957
4072
|
const cleanlyCompleted = this.#cleanlyCompletedBlocks.has(blockKey);
|
|
4073
|
+
if (esi !== (this.#workerRecoveredNextEsi.get(blockKey) ?? 0)) {
|
|
4074
|
+
this.#refusedRecoveries.add(blockKey);
|
|
4075
|
+
if (this.#refusedRecoveries.size > 1e3) dropOldest(this.#refusedRecoveries, 500);
|
|
4076
|
+
}
|
|
4077
|
+
if (esi >= meta.ssbLength - 1) {
|
|
4078
|
+
this.#workerRecoveredNextEsi.delete(blockKey);
|
|
4079
|
+
} else {
|
|
4080
|
+
this.#workerRecoveredNextEsi.set(blockKey, esi + 1);
|
|
4081
|
+
if (this.#workerRecoveredNextEsi.size > 1e3) dropOldest(this.#workerRecoveredNextEsi, 500);
|
|
4082
|
+
}
|
|
3958
4083
|
if (!cleanlyCompleted) {
|
|
3959
|
-
|
|
4084
|
+
const symbolKey = `${blockKey}:${esi}`;
|
|
4085
|
+
if (!this.#seenSymbols.has(symbolKey)) {
|
|
4086
|
+
this.#seenSymbols.add(symbolKey);
|
|
4087
|
+
const admitted = this.onBlockRecovered?.(
|
|
4088
|
+
meta.sbn,
|
|
4089
|
+
packetId,
|
|
4090
|
+
data,
|
|
4091
|
+
meta,
|
|
4092
|
+
recoveredCatalogTrackName
|
|
4093
|
+
);
|
|
4094
|
+
if (admitted === false) {
|
|
4095
|
+
this.#refusedRecoveries.add(blockKey);
|
|
4096
|
+
if (this.#refusedRecoveries.size > 1e3) dropOldest(this.#refusedRecoveries, 500);
|
|
4097
|
+
}
|
|
4098
|
+
}
|
|
4099
|
+
}
|
|
4100
|
+
const refused = esi >= meta.ssbLength - 1 && this.#refusedRecoveries.delete(blockKey);
|
|
4101
|
+
if (esi >= meta.ssbLength - 1 && !refused && !this.#completedBlocks.has(blockKey)) {
|
|
4102
|
+
if (!cleanlyCompleted && !this.#workerRecoveredBlocks.has(blockKey)) {
|
|
3960
4103
|
this.#traceLifecycle(
|
|
3961
4104
|
"recovered",
|
|
3962
4105
|
meta.sbn,
|
|
@@ -3971,19 +4114,6 @@ var FECManager = class _FECManager {
|
|
|
3971
4114
|
}
|
|
3972
4115
|
this.#noteBlockRepaired(blockKey, meta.sbn, packetId, recoveredCatalogTrackName);
|
|
3973
4116
|
}
|
|
3974
|
-
const symbolKey = `${blockKey}:${esi}`;
|
|
3975
|
-
if (!this.#seenSymbols.has(symbolKey)) {
|
|
3976
|
-
this.#seenSymbols.add(symbolKey);
|
|
3977
|
-
this.onBlockRecovered?.(
|
|
3978
|
-
meta.sbn,
|
|
3979
|
-
packetId,
|
|
3980
|
-
data,
|
|
3981
|
-
meta,
|
|
3982
|
-
recoveredCatalogTrackName
|
|
3983
|
-
);
|
|
3984
|
-
}
|
|
3985
|
-
}
|
|
3986
|
-
if (esi >= meta.ssbLength - 1 && !this.#completedBlocks.has(blockKey)) {
|
|
3987
4117
|
this.cancelBlockTimeout(meta.sbn, packetId, recoveredCatalogTrackName);
|
|
3988
4118
|
if (recoveredCatalogTrackName === void 0) {
|
|
3989
4119
|
this.onBlockComplete?.(meta.sbn, packetId);
|
|
@@ -4278,66 +4408,26 @@ var FECManager = class _FECManager {
|
|
|
4278
4408
|
const config = this.#trackConfigs.get(packetId);
|
|
4279
4409
|
const layers = config?.repairLayers;
|
|
4280
4410
|
if (layers === void 0 || layers.length === 0) return true;
|
|
4281
|
-
|
|
4282
|
-
|
|
4411
|
+
let admission = this.#heldLayerStates.get(streamKey);
|
|
4412
|
+
if (admission === void 0) {
|
|
4413
|
+
admission = new HeldLayerAdmission(layers, config.heldLayers);
|
|
4414
|
+
this.#heldLayerStates.set(streamKey, admission);
|
|
4283
4415
|
}
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
this.#
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
}
|
|
4300
|
-
} else {
|
|
4301
|
-
if (state.newest === void 0 || serial32Precedes(state.newest, ssStart)) state.newest = ssStart;
|
|
4302
|
-
const stale = (s) => serial32Distance(s, state.newest) > horizon;
|
|
4303
|
-
if (state.latestBelow !== void 0 && stale(state.latestBelow)) {
|
|
4304
|
-
delete state.latestBelow;
|
|
4305
|
-
delete state.latestBelowRsb;
|
|
4306
|
-
}
|
|
4307
|
-
if (state.anchor !== void 0 && stale(state.anchor)) state.settled = true;
|
|
4308
|
-
if (rsbLength < state.total) {
|
|
4309
|
-
if (state.layer > 0 && repairTrackName === state.repairTrackName) code = "below_own_track_total";
|
|
4310
|
-
else if (stale(ssStart)) continue;
|
|
4311
|
-
else if (state.settled || state.anchor !== void 0 && !serial32Precedes(ssStart, state.anchor)) code = "below_anchored_total";
|
|
4312
|
-
else if (state.latestBelow === void 0 || serial32Precedes(state.latestBelow, ssStart)) {
|
|
4313
|
-
state.latestBelow = ssStart;
|
|
4314
|
-
state.latestBelowRsb = rsbLength;
|
|
4315
|
-
}
|
|
4316
|
-
} else if (!state.settled && !stale(ssStart)) {
|
|
4317
|
-
if (state.anchor === void 0 || serial32Precedes(ssStart, state.anchor)) state.anchor = ssStart;
|
|
4318
|
-
if (state.latestBelow !== void 0 && !serial32Precedes(state.latestBelow, state.anchor)) {
|
|
4319
|
-
code = "below_anchored_total";
|
|
4320
|
-
ssStart = state.latestBelow;
|
|
4321
|
-
rsbLength = state.latestBelowRsb;
|
|
4322
|
-
}
|
|
4323
|
-
}
|
|
4324
|
-
}
|
|
4325
|
-
if (code !== void 0) {
|
|
4326
|
-
const reason = {
|
|
4327
|
-
code,
|
|
4328
|
-
layer: state.layer,
|
|
4329
|
-
total: state.total,
|
|
4330
|
-
rsbLength,
|
|
4331
|
-
ssStart,
|
|
4332
|
-
...state.anchor === void 0 ? {} : { anchor: state.anchor }
|
|
4333
|
-
};
|
|
4334
|
-
this.#fecFailClosed.set(streamKey, reason);
|
|
4335
|
-
this.#onFecFailClosed?.({ ...reason, packetId, catalogTrackName });
|
|
4336
|
-
console.warn(
|
|
4337
|
-
`[FECManager] FEC failed closed (fec-01 6.3.9) packetId=${packetId} track=${catalogTrackName ?? "-"}: ${JSON.stringify(reason)}`
|
|
4338
|
-
);
|
|
4339
|
-
return false;
|
|
4340
|
-
}
|
|
4416
|
+
const verdict = admission.admit(
|
|
4417
|
+
repairTrackName,
|
|
4418
|
+
ssStart,
|
|
4419
|
+
rsbLength,
|
|
4420
|
+
this.#layerAnchorHorizon(packetId),
|
|
4421
|
+
this.#sourceFrontiers.get(streamKey)?.sbn
|
|
4422
|
+
);
|
|
4423
|
+
if (verdict === "discard") return false;
|
|
4424
|
+
if (verdict !== void 0) {
|
|
4425
|
+
this.#fecFailClosed.set(streamKey, verdict);
|
|
4426
|
+
this.#onFecFailClosed?.({ ...verdict, packetId, catalogTrackName });
|
|
4427
|
+
console.warn(
|
|
4428
|
+
`[FECManager] FEC failed closed (fec-01 6.3.9) packetId=${packetId} track=${catalogTrackName ?? "-"}: ${JSON.stringify(verdict)}`
|
|
4429
|
+
);
|
|
4430
|
+
return false;
|
|
4341
4431
|
}
|
|
4342
4432
|
return true;
|
|
4343
4433
|
}
|
|
@@ -4347,15 +4437,13 @@ var FECManager = class _FECManager {
|
|
|
4347
4437
|
* times K, capped at 2^30 so compared serials stay inside one half-window.
|
|
4348
4438
|
*/
|
|
4349
4439
|
#layerAnchorHorizon(packetId) {
|
|
4350
|
-
const cap = 1073741824;
|
|
4351
4440
|
let blocks;
|
|
4352
4441
|
try {
|
|
4353
4442
|
blocks = this.isoRetentionBlocksForPacket(packetId);
|
|
4354
4443
|
} catch {
|
|
4355
4444
|
blocks = void 0;
|
|
4356
4445
|
}
|
|
4357
|
-
|
|
4358
|
-
return blocks === void 0 || k <= 0 ? cap : Math.min(cap, blocks * k);
|
|
4446
|
+
return heldLayerAnchorHorizon(blocks, this.#trackConfigs.get(packetId)?.k ?? 0);
|
|
4359
4447
|
}
|
|
4360
4448
|
/**
|
|
4361
4449
|
* Report each stream whose FEC fails closed at runtime (fec-01 6.3.9), once,
|
|
@@ -4595,6 +4683,33 @@ var FECManager = class _FECManager {
|
|
|
4595
4683
|
}
|
|
4596
4684
|
queueMicrotask(() => this.#syncDecoderBlockOwners.delete(key));
|
|
4597
4685
|
}
|
|
4686
|
+
/**
|
|
4687
|
+
* Run `fn` with `receivedAtMs` (performance.now() clock) as the receipt
|
|
4688
|
+
* time of what it feeds. Within a consumer the bc-provenance profile
|
|
4689
|
+
* governs, a datagram held on a miss and admitted later keeps its receipt
|
|
4690
|
+
* time (fec-01 9): it anchors its block's ts and arms the block's
|
|
4691
|
+
* deadlines from then, never later than they are already armed.
|
|
4692
|
+
*/
|
|
4693
|
+
withReceiptTime(receivedAtMs, fn) {
|
|
4694
|
+
const outer = this.#receiptAtMs;
|
|
4695
|
+
this.#receiptAtMs = receivedAtMs;
|
|
4696
|
+
try {
|
|
4697
|
+
fn();
|
|
4698
|
+
} finally {
|
|
4699
|
+
this.#receiptAtMs = outer;
|
|
4700
|
+
}
|
|
4701
|
+
}
|
|
4702
|
+
#receiptNow() {
|
|
4703
|
+
return this.#receiptAtMs ?? performance.now();
|
|
4704
|
+
}
|
|
4705
|
+
/**
|
|
4706
|
+
* A held packet admitted late arms from its (earlier) receipt time; that
|
|
4707
|
+
* never pulls in a deadline later activity already armed. Outside a
|
|
4708
|
+
* receipt scope a re-arm replaces the deadline, as a narrower bound may.
|
|
4709
|
+
*/
|
|
4710
|
+
#notEarlierUnderReceipt(deadlineMs, armedMs) {
|
|
4711
|
+
return this.#receiptAtMs === void 0 || armedMs === void 0 ? deadlineMs : Math.max(deadlineMs, armedMs);
|
|
4712
|
+
}
|
|
4598
4713
|
/** Refresh deadlines after accepted block activity. */
|
|
4599
4714
|
noteBlockActivity(sbn, packetId, catalogTrackName, sourceProgress = false) {
|
|
4600
4715
|
if (!this.#trackConfigs.has(packetId)) return;
|
|
@@ -4773,6 +4888,8 @@ var FECManager = class _FECManager {
|
|
|
4773
4888
|
this.#cleanlyCompletedBlocks.clear();
|
|
4774
4889
|
this.#repairRequestedBlocks.clear();
|
|
4775
4890
|
this.#workerRecoveredBlocks.clear();
|
|
4891
|
+
this.#refusedRecoveries.clear();
|
|
4892
|
+
this.#workerRecoveredNextEsi.clear();
|
|
4776
4893
|
this.#syncDecoderBlockOwners.clear();
|
|
4777
4894
|
this.#startupAlignedPackets.clear();
|
|
4778
4895
|
this.#startupPartialBlocks.clear();
|
|
@@ -7618,11 +7735,11 @@ function hevcNalUnitsContainIrap(nals) {
|
|
|
7618
7735
|
return sawNal ? false : void 0;
|
|
7619
7736
|
}
|
|
7620
7737
|
function prepareHevcCmafSample(data, sampleEntry) {
|
|
7621
|
-
const
|
|
7622
|
-
if (
|
|
7623
|
-
const keyframe = hevcNalUnitsContainIrap(
|
|
7738
|
+
const split2 = splitHevcSample(data);
|
|
7739
|
+
if (split2 === null) return null;
|
|
7740
|
+
const keyframe = hevcNalUnitsContainIrap(split2.nals);
|
|
7624
7741
|
if (keyframe === void 0) return null;
|
|
7625
|
-
const normalized = hevcNalUnitsToLengthPrefixed(data,
|
|
7742
|
+
const normalized = hevcNalUnitsToLengthPrefixed(data, split2.nals, split2.lengthPrefixed, sampleEntry);
|
|
7626
7743
|
return normalized === null ? null : { keyframe, data: normalized };
|
|
7627
7744
|
}
|
|
7628
7745
|
function hevcNalUnitsToLengthPrefixed(data, nals, lengthPrefixed, sampleEntry) {
|
|
@@ -9033,9 +9150,9 @@ var CmafBlockAssembler = class {
|
|
|
9033
9150
|
const sample = splitH264SampleNalUnits(data);
|
|
9034
9151
|
const nalTypes = describeH264NalTypes(sample.nals);
|
|
9035
9152
|
const classify = classifyH264Keyframe(data);
|
|
9036
|
-
const
|
|
9153
|
+
const hex2 = Array.from(data.subarray(0, Math.min(48, data.length))).map((b) => b.toString(16).padStart(2, "0")).join(" ");
|
|
9037
9154
|
console.log(
|
|
9038
|
-
`[L4-KF-GATE] reject#${this.#keyframeRejectDumpCount} len=${data.length} metadataKf=${metadataKeyframe} classify=${classify} format=${sample.format} nalTypes=[${nalTypes.join(",")}] first48=[${
|
|
9155
|
+
`[L4-KF-GATE] reject#${this.#keyframeRejectDumpCount} len=${data.length} metadataKf=${metadataKeyframe} classify=${classify} format=${sample.format} nalTypes=[${nalTypes.join(",")}] first48=[${hex2}]`
|
|
9039
9156
|
);
|
|
9040
9157
|
}
|
|
9041
9158
|
/** Mark video as already synchronized by an external decoder path.
|
|
@@ -11003,6 +11120,9 @@ function authoredGap(before, after) {
|
|
|
11003
11120
|
if (!a || !b || a.mediaTimescale !== b.mediaTimescale) return false;
|
|
11004
11121
|
return b.decodeTime > a.decodeTime + BigInt(a.durationTicks);
|
|
11005
11122
|
}
|
|
11123
|
+
function ssIdAfter(a, b) {
|
|
11124
|
+
return (a - b | 0) > 0;
|
|
11125
|
+
}
|
|
11006
11126
|
var FecReleaseSequencer = class {
|
|
11007
11127
|
#streams = /* @__PURE__ */ new Map();
|
|
11008
11128
|
#gate;
|
|
@@ -11064,11 +11184,17 @@ var FecReleaseSequencer = class {
|
|
|
11064
11184
|
stream.last = null;
|
|
11065
11185
|
stream.lastSbn = void 0;
|
|
11066
11186
|
stream.discontinuity = false;
|
|
11187
|
+
stream.rapSsId = void 0;
|
|
11067
11188
|
}
|
|
11068
11189
|
if (stream.last !== null && compareFrameOrder(frame, stream.last) <= 0) {
|
|
11069
11190
|
this.#recordStat("droppedStale");
|
|
11070
11191
|
return;
|
|
11071
11192
|
}
|
|
11193
|
+
const ssId = meta.fecSbn;
|
|
11194
|
+
if (meta.trackType === "video" && ssId !== void 0 && this.#gate.isRap?.(data, meta) === true) {
|
|
11195
|
+
if (stream.rapSsId === void 0 || ssIdAfter(ssId, stream.rapSsId)) stream.rapSsId = ssId;
|
|
11196
|
+
this.#gate.onRap?.(stream.packetId, stream.catalogTrackName, ssId);
|
|
11197
|
+
}
|
|
11072
11198
|
if (stream.last === null) {
|
|
11073
11199
|
if (meta.trackType === "video" && !meta.keyframe) {
|
|
11074
11200
|
this.#recordStat("droppedBeforeStartupKeyframe");
|
|
@@ -11104,6 +11230,22 @@ var FecReleaseSequencer = class {
|
|
|
11104
11230
|
}
|
|
11105
11231
|
return earliest;
|
|
11106
11232
|
}
|
|
11233
|
+
/**
|
|
11234
|
+
* Whether a complete RAP above SS_ID `ssId` entered the gate of a stream of
|
|
11235
|
+
* `packetId` (draft-ramadan-moq-fec 11.4.2: repair of a block below it is
|
|
11236
|
+
* moot). Its SS_ID is the one its frame completed in; a frame's packets
|
|
11237
|
+
* hold consecutive SS_IDs in authored order, so a complete RAP with a
|
|
11238
|
+
* packet above `ssId` has every packet of it at or above the first SS_ID
|
|
11239
|
+
* any loss at or below `ssId` could have taken.
|
|
11240
|
+
*/
|
|
11241
|
+
rapAbove(packetId, catalogTrackName, ssId) {
|
|
11242
|
+
for (const stream of this.#streams.values()) {
|
|
11243
|
+
if (stream.packetId !== packetId) continue;
|
|
11244
|
+
if (catalogTrackName !== void 0 && stream.catalogTrackName !== catalogTrackName) continue;
|
|
11245
|
+
if (stream.rapSsId !== void 0 && ssIdAfter(stream.rapSsId, ssId)) return true;
|
|
11246
|
+
}
|
|
11247
|
+
return false;
|
|
11248
|
+
}
|
|
11107
11249
|
/** Re-evaluate held gaps after FEC state changed. */
|
|
11108
11250
|
poke(streamKey) {
|
|
11109
11251
|
if (streamKey !== void 0) {
|
|
@@ -11194,7 +11336,8 @@ var FecReleaseSequencer = class {
|
|
|
11194
11336
|
buf: [],
|
|
11195
11337
|
last: null,
|
|
11196
11338
|
lastSbn: void 0,
|
|
11197
|
-
discontinuity: false
|
|
11339
|
+
discontinuity: false,
|
|
11340
|
+
rapSsId: void 0
|
|
11198
11341
|
};
|
|
11199
11342
|
this.#streams.set(streamKey, stream);
|
|
11200
11343
|
}
|
|
@@ -11557,6 +11700,10 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
11557
11700
|
fecReleaseEarliestHeldAtMs(packetId, catalogTrackName) {
|
|
11558
11701
|
return this.#fecSequencer?.earliestHeldArrivalMs(packetId, catalogTrackName);
|
|
11559
11702
|
}
|
|
11703
|
+
/** See FecReleaseSequencer.rapAbove; false when no stream is FEC-gated. */
|
|
11704
|
+
fecReleaseRapAbove(packetId, catalogTrackName, ssId) {
|
|
11705
|
+
return this.#fecSequencer?.rapAbove(packetId, catalogTrackName, ssId) ?? false;
|
|
11706
|
+
}
|
|
11560
11707
|
/** Re-evaluate held gaps after FEC block state changed. */
|
|
11561
11708
|
pokeFecRelease(packetId, catalogTrackName) {
|
|
11562
11709
|
if (!this.#fecSequencer) return;
|
|
@@ -12826,6 +12973,10 @@ var NO_BLOCK_SPAN_HORIZON = Object.freeze({
|
|
|
12826
12973
|
reason: "no-block-span"
|
|
12827
12974
|
});
|
|
12828
12975
|
var DIRECT_RECOVERED_VIDEO_QUEUE_LIMIT = 512;
|
|
12976
|
+
var ASSIST_BLOCK_LIMIT = 64;
|
|
12977
|
+
var ASSIST_FAILURE_LEDGER_LIMIT = 4 * ASSIST_BLOCK_LIMIT;
|
|
12978
|
+
var ASSIST_FAILED_DECODES_MAX = 2;
|
|
12979
|
+
var assistKey = (ownerPacketId, catalogTrackName, blockStart) => `${ownerPacketId}/${catalogTrackName ?? ""}/${blockStart}`;
|
|
12829
12980
|
var DIRECT_RECOVERED_AUDIO_QUEUE_LIMIT = 512;
|
|
12830
12981
|
function sourceSymbolEvidence(packet, header, rapFlag, packetSequenceNumber) {
|
|
12831
12982
|
const identity = MmtpContainerParser.sourcePacketIdentity(packet, header);
|
|
@@ -12846,11 +12997,27 @@ var MmtpRouter = class {
|
|
|
12846
12997
|
#fecManager;
|
|
12847
12998
|
#defaultFecMapper;
|
|
12848
12999
|
#fecMappersByPacketId = /* @__PURE__ */ new Map();
|
|
12849
|
-
|
|
12850
|
-
#
|
|
12851
|
-
#
|
|
12852
|
-
#
|
|
12853
|
-
#
|
|
13000
|
+
/** Protected flows: one 7.2.7 receiver each, and the ingress its datagrams arrive with. */
|
|
13001
|
+
#provenance = /* @__PURE__ */ new Map();
|
|
13002
|
+
#onFlowProtected = null;
|
|
13003
|
+
#unadmittedListedDrops = 0;
|
|
13004
|
+
#unlistedRepairSetAside = 0;
|
|
13005
|
+
#assistBlocks = /* @__PURE__ */ new Map();
|
|
13006
|
+
#assistFailures = /* @__PURE__ */ new Map();
|
|
13007
|
+
#scratchDecoderFactory = null;
|
|
13008
|
+
#unlistedAssistedRecoveries = 0;
|
|
13009
|
+
#unlistedAssistedRefusals = 0;
|
|
13010
|
+
/** The ingress a held datagram arrived with; the receiver returns the same bytes on release. */
|
|
13011
|
+
#heldIngress = /* @__PURE__ */ new WeakMap();
|
|
13012
|
+
#unicastRepair = false;
|
|
13013
|
+
/** How to route a held unicast (MoQ) datagram once a record admits it. */
|
|
13014
|
+
#heldReplay = /* @__PURE__ */ new WeakMap();
|
|
13015
|
+
/** When a held datagram was received: admitted later, it keeps that receipt time (fec-01 9). */
|
|
13016
|
+
#heldAt = /* @__PURE__ */ new WeakMap();
|
|
13017
|
+
/** The flow whose receiver admitted a stream's source datagrams, by packetId then catalog track name: recovered packets answer to it. */
|
|
13018
|
+
#provenanceStreamFlow = /* @__PURE__ */ new Map();
|
|
13019
|
+
#unregisteredFlowDrops = 0;
|
|
13020
|
+
#ambiguousRecoveredDrops = 0;
|
|
12854
13021
|
// Temp state set by #processMMTP before calling parser.feed(),
|
|
12855
13022
|
// read by the parser's onFrame callback for CMAF assembly + dedup.
|
|
12856
13023
|
#currentFecInfo;
|
|
@@ -12932,6 +13099,7 @@ var MmtpRouter = class {
|
|
|
12932
13099
|
#onBlockFailed = null;
|
|
12933
13100
|
/** Typed observer of every gap decision the release gate makes. */
|
|
12934
13101
|
#onFrameGap;
|
|
13102
|
+
#fecRapObservers = /* @__PURE__ */ new Set();
|
|
12935
13103
|
// Pokes requested while a packet is being processed are deferred to its
|
|
12936
13104
|
// end: a synchronous recovery completes its block BEFORE routing the
|
|
12937
13105
|
// recovered frames, and releasing a held gap in between would strand them.
|
|
@@ -12987,10 +13155,6 @@ var MmtpRouter = class {
|
|
|
12987
13155
|
this.#fecManager = config?.fecManager ?? null;
|
|
12988
13156
|
this.#onFrameGap = config?.onFrameGap ?? null;
|
|
12989
13157
|
this.#defaultFecMapper = config?.fecMapper ?? null;
|
|
12990
|
-
this.#manifestVerifier = config?.manifestVerifier ?? null;
|
|
12991
|
-
this.#ownsManifestVerifier = config?.manifestVerifierOwnership !== "caller";
|
|
12992
|
-
this.#resolveManifestIdentity = config?.resolveManifestIdentity ?? null;
|
|
12993
|
-
this.#resolveManifestIdentityForRoute = config?.resolveManifestIdentityForRoute ?? null;
|
|
12994
13158
|
this.#parser = new MmtpContainerParser({
|
|
12995
13159
|
mfuTimeoutMs: config?.mfuTimeoutMs,
|
|
12996
13160
|
reassemblyPolicy: config?.reassemblyPolicy,
|
|
@@ -13522,29 +13686,310 @@ var MmtpRouter = class {
|
|
|
13522
13686
|
}
|
|
13523
13687
|
});
|
|
13524
13688
|
}
|
|
13525
|
-
|
|
13526
|
-
|
|
13527
|
-
|
|
13528
|
-
|
|
13529
|
-
|
|
13530
|
-
|
|
13531
|
-
|
|
13532
|
-
|
|
13533
|
-
this.#
|
|
13534
|
-
|
|
13535
|
-
|
|
13536
|
-
|
|
13537
|
-
|
|
13538
|
-
|
|
13539
|
-
|
|
13540
|
-
|
|
13541
|
-
|
|
13542
|
-
|
|
13543
|
-
|
|
13689
|
+
/**
|
|
13690
|
+
* Protect a flow (draft-ramadan-moq-multicast-01 7.2.7): every multicast
|
|
13691
|
+
* datagram whose ingress names `ingress.flow`, source and repair alike, is
|
|
13692
|
+
* admitted by `receiver` before anything reads it, and an unadmitted one is
|
|
13693
|
+
* held or dropped. The caller owns the receiver's configuration; the router
|
|
13694
|
+
* releases it on `releaseProvenanceFlow` and on dispose.
|
|
13695
|
+
*/
|
|
13696
|
+
registerProvenanceFlow(ingress, receiver) {
|
|
13697
|
+
this.#provenance.get(ingress.flow)?.receiver.release();
|
|
13698
|
+
this.#provenance.set(ingress.flow, { receiver, ingress });
|
|
13699
|
+
receiver.keepPendingAgeMs = () => this.#oldestOpenBlockAgeMs(ingress.flow);
|
|
13700
|
+
}
|
|
13701
|
+
/**
|
|
13702
|
+
* Declare that this receiver can request unicast repair (a repair FETCH
|
|
13703
|
+
* scheduler is wired): a protected flow then keeps the pending entries of
|
|
13704
|
+
* its open blocks for the repair to consume (7.2.7 step 2).
|
|
13705
|
+
*/
|
|
13706
|
+
setUnicastRepair(enabled) {
|
|
13707
|
+
this.#unicastRepair = enabled;
|
|
13708
|
+
}
|
|
13709
|
+
#oldestOpenBlockAgeMs(flow) {
|
|
13710
|
+
const entry = this.#provenance.get(flow);
|
|
13711
|
+
if (!this.#unicastRepair || !entry) return void 0;
|
|
13712
|
+
let age;
|
|
13713
|
+
for (const { name, packetId } of entry.ingress.tracks) {
|
|
13714
|
+
const a = name ? this.#fecManager?.oldestOpenBlockAgeMs?.(packetId, name) : void 0;
|
|
13715
|
+
if (a !== void 0 && (age === void 0 || a > age)) age = a;
|
|
13716
|
+
}
|
|
13717
|
+
return age;
|
|
13718
|
+
}
|
|
13719
|
+
/**
|
|
13720
|
+
* Replace a registered flow's ingress (a rendition switch staged, committed
|
|
13721
|
+
* or cancelled) while keeping its receiver; a no-op for an unregistered flow.
|
|
13722
|
+
*/
|
|
13723
|
+
updateProvenanceFlowIngress(ingress) {
|
|
13724
|
+
const entry = this.#provenance.get(ingress.flow);
|
|
13725
|
+
if (!entry) return;
|
|
13726
|
+
const added = entry.protected ? ingress.tracks.filter(({ name, packetId }) => name && !entry.ingress.tracks.some((t) => t.name === name && t.packetId === packetId)) : [];
|
|
13727
|
+
const governed = added.map(({ packetId }) => [...this.#repairOwnersByPacketId.get(packetId) ?? [packetId]].some((owner) => this.#governs(owner)));
|
|
13728
|
+
this.#provenance.set(ingress.flow, { ...entry, ingress });
|
|
13729
|
+
added.forEach(({ name, packetId }, i) => {
|
|
13730
|
+
if (governed[i]) for (const block of this.#assistBlocks.values()) block.pooled.clear();
|
|
13731
|
+
else this.#fecManager?.resetTrack?.(packetId, name);
|
|
13732
|
+
});
|
|
13733
|
+
}
|
|
13734
|
+
/**
|
|
13735
|
+
* Called once per protection of a flow: at the admission that makes this
|
|
13736
|
+
* router's consumers protected under it (7.2.7), after their unadmitted
|
|
13737
|
+
* holdings of its listed tracks are discarded. Again after an unprotect.
|
|
13738
|
+
*/
|
|
13739
|
+
onProvenanceFlowProtected(cb) {
|
|
13740
|
+
this.#onFlowProtected = cb;
|
|
13741
|
+
}
|
|
13742
|
+
/**
|
|
13743
|
+
* The receiver ceased to hold the flow's endpoint on failover (7.2.7): drop
|
|
13744
|
+
* its state and hold queues (no expiry counted) and un-protect its tracks,
|
|
13745
|
+
* which then take MoQ again. The registration stays, so multicast resuming
|
|
13746
|
+
* protects the flow anew at its next admission.
|
|
13747
|
+
*/
|
|
13748
|
+
unprotectProvenanceFlow(flow) {
|
|
13749
|
+
const entry = this.#provenance.get(flow);
|
|
13750
|
+
if (!entry) return;
|
|
13751
|
+
entry.receiver.release();
|
|
13752
|
+
entry.protected = false;
|
|
13753
|
+
this.#assistBlocks.clear();
|
|
13754
|
+
this.#assistFailures.clear();
|
|
13755
|
+
}
|
|
13756
|
+
/** MoQ packets of a track a protected flow lists, dropped on its unicast path: duplicate, overflow, or listed by more than one flow. */
|
|
13757
|
+
get unadmittedListedDropCount() {
|
|
13758
|
+
return this.#unadmittedListedDrops;
|
|
13759
|
+
}
|
|
13760
|
+
/** MoQ repair symbols of an unlisted track kept out of a governed FEC instance (7.2.7 step 2). */
|
|
13761
|
+
get unlistedRepairSetAsideCount() {
|
|
13762
|
+
return this.#unlistedRepairSetAside;
|
|
13763
|
+
}
|
|
13764
|
+
/** Governed blocks recovered with the help of unlisted repair (7.2.7 step 2). */
|
|
13765
|
+
get unlistedAssistedRecoveryCount() {
|
|
13766
|
+
return this.#unlistedAssistedRecoveries;
|
|
13767
|
+
}
|
|
13768
|
+
/** Unlisted-assisted decodes refused because two already failed for the block (7.2.7 step 2). */
|
|
13769
|
+
get unlistedAssistedRefusalCount() {
|
|
13770
|
+
return this.#unlistedAssistedRefusals;
|
|
13771
|
+
}
|
|
13772
|
+
/** Unlisted repair symbols pooled now, across every governed block (7.2.7 step 2). */
|
|
13773
|
+
get unlistedPoolSymbolCount() {
|
|
13774
|
+
let n = 0;
|
|
13775
|
+
for (const block of this.#assistBlocks.values()) n += block.pooled.size;
|
|
13776
|
+
return n;
|
|
13777
|
+
}
|
|
13778
|
+
/**
|
|
13779
|
+
* Wire the decoder an unlisted-assisted decode runs in: a fresh, configured
|
|
13780
|
+
* decoder of the owner's track, used once and discarded, never the live one.
|
|
13781
|
+
* Without it unlisted repair on a governed instance is set aside and dropped.
|
|
13782
|
+
*/
|
|
13783
|
+
setScratchFecDecoderFactory(factory) {
|
|
13784
|
+
this.#scratchDecoderFactory = factory;
|
|
13785
|
+
if (!factory) {
|
|
13786
|
+
this.#assistBlocks.clear();
|
|
13787
|
+
this.#assistFailures.clear();
|
|
13788
|
+
}
|
|
13789
|
+
}
|
|
13790
|
+
/** Whether a protected flow lists `ownerPacketId`: its FEC instance is governed (7.2.7). */
|
|
13791
|
+
#governs(ownerPacketId) {
|
|
13792
|
+
for (const entry of this.#provenance.values()) {
|
|
13793
|
+
if (entry.protected && ingressHasPacketId(entry.ingress, ownerPacketId)) return true;
|
|
13794
|
+
}
|
|
13795
|
+
return false;
|
|
13796
|
+
}
|
|
13797
|
+
/**
|
|
13798
|
+
* A MoQ repair symbol no protected flow lists, owned by a source track one
|
|
13799
|
+
* does: its FEC instance is governed (7.2.7 step 2) and takes only admitted
|
|
13800
|
+
* symbols, so the symbol is set aside: pooled for an unlisted-assisted
|
|
13801
|
+
* decode of a block admitted repair already delimits, else dropped. It
|
|
13802
|
+
* never states a block, so the live locator and decoder are unchanged.
|
|
13803
|
+
* Un-listing needs no hook: an unprotected or released flow lists nothing.
|
|
13804
|
+
*/
|
|
13805
|
+
#setAsideUnlistedRepair(ownerPacketId, catalogTrackName, repair) {
|
|
13806
|
+
if (!this.#governs(ownerPacketId)) return false;
|
|
13807
|
+
this.#unlistedRepairSetAside++;
|
|
13808
|
+
const key = assistKey(ownerPacketId, catalogTrackName, repair.ssStart);
|
|
13809
|
+
const block = this.#assistBlocks.get(key);
|
|
13810
|
+
if (block !== void 0 && block.ssbLength === repair.ssbLength && !block.repairs.has(repair.rsId) && block.pooled.size < block.ssbLength && repair.symbolData.byteLength === this.#symbolSizeForPacket(ownerPacketId) && this.#fecManager?.admitRepairBlockLength?.(ownerPacketId, repair.ssbLength, repair.rsbLength, repair.rsId) !== false) {
|
|
13811
|
+
block.pooled.set(repair.rsId, repair.symbolData);
|
|
13812
|
+
this.#tryAssistedDecode(key, block, ownerPacketId, catalogTrackName, repair.ssStart);
|
|
13813
|
+
}
|
|
13814
|
+
return true;
|
|
13815
|
+
}
|
|
13816
|
+
/**
|
|
13817
|
+
* An admitted repair placed a governed block (its SSB_length delimits it):
|
|
13818
|
+
* open the block's record, which the symbols its live decoder takes join.
|
|
13819
|
+
*/
|
|
13820
|
+
#openAssistBlock(ownerPacketId, catalogTrackName, blockStart, ssbLength) {
|
|
13821
|
+
const key = assistKey(ownerPacketId, catalogTrackName, blockStart);
|
|
13822
|
+
if (!this.#scratchDecoderFactory || this.#assistBlocks.has(key) || !this.#governs(ownerPacketId)) return;
|
|
13823
|
+
this.#assistBlocks.set(key, { ssbLength, sources: /* @__PURE__ */ new Map(), repairs: /* @__PURE__ */ new Map(), pooled: /* @__PURE__ */ new Map() });
|
|
13824
|
+
for (const oldest of this.#assistBlocks.keys()) {
|
|
13825
|
+
if (this.#assistBlocks.size <= ASSIST_BLOCK_LIMIT) break;
|
|
13826
|
+
this.#assistBlocks.delete(oldest);
|
|
13827
|
+
}
|
|
13828
|
+
}
|
|
13829
|
+
/** A symbol fed to an open governed block's live decoder that did not recover it. */
|
|
13830
|
+
#noteAssistAdmitted(ownerPacketId, catalogTrackName, blockStart, symbol, data) {
|
|
13831
|
+
const key = assistKey(ownerPacketId, catalogTrackName, blockStart);
|
|
13832
|
+
const block = this.#assistBlocks.get(key);
|
|
13833
|
+
if (block === void 0) return;
|
|
13834
|
+
if ("rsId" in symbol) {
|
|
13835
|
+
block.repairs.set(symbol.rsId, data);
|
|
13836
|
+
block.pooled.delete(symbol.rsId);
|
|
13837
|
+
} else {
|
|
13838
|
+
block.sources.set(symbol.esi, data);
|
|
13839
|
+
}
|
|
13840
|
+
this.#tryAssistedDecode(key, block, ownerPacketId, catalogTrackName, blockStart);
|
|
13841
|
+
}
|
|
13842
|
+
/**
|
|
13843
|
+
* 7.2.7 step 2, phase two: a governed block its admitted symbols cannot
|
|
13844
|
+
* decode (the live decoder already had them all) is decoded in a scratch
|
|
13845
|
+
* decoder from its admitted symbols plus pooled unlisted repair, only as
|
|
13846
|
+
* many as close the shortfall. What it recovers is released only through
|
|
13847
|
+
* admission (#routeRecoveredBlock), so the live decoder is never touched and
|
|
13848
|
+
* a refused recovery leaves the block to fail at its FEC deadline. At most
|
|
13849
|
+
* two such decodes may fail per block; a third is refused.
|
|
13850
|
+
*/
|
|
13851
|
+
#tryAssistedDecode(key, block, ownerPacketId, catalogTrackName, blockStart) {
|
|
13852
|
+
const fec = this.#fecManager;
|
|
13853
|
+
const admitted = block.sources.size + block.repairs.size;
|
|
13854
|
+
if (!fec || block.pooled.size === 0) return;
|
|
13855
|
+
if (admitted >= block.ssbLength || admitted + block.pooled.size < block.ssbLength) return;
|
|
13856
|
+
if (fec.isBlockCompleted(blockStart, ownerPacketId, catalogTrackName)) {
|
|
13857
|
+
this.#assistBlocks.delete(key);
|
|
13858
|
+
return;
|
|
13859
|
+
}
|
|
13860
|
+
if ((this.#assistFailures.get(key) ?? 0) >= ASSIST_FAILED_DECODES_MAX) {
|
|
13861
|
+
this.#unlistedAssistedRefusals++;
|
|
13862
|
+
return;
|
|
13863
|
+
}
|
|
13864
|
+
const decoder = this.#scratchDecoderFactory?.(ownerPacketId, catalogTrackName);
|
|
13865
|
+
if (!decoder) return;
|
|
13866
|
+
let recovered = null;
|
|
13867
|
+
const fed = [];
|
|
13868
|
+
try {
|
|
13869
|
+
for (const [esi, data] of block.sources) decoder.add_source(serial32Add(blockStart, esi), data, 0);
|
|
13870
|
+
for (const [rsId, data] of block.repairs) {
|
|
13871
|
+
recovered ??= decoder.add_repair(blockStart, block.ssbLength, rsId, data, 0);
|
|
13872
|
+
}
|
|
13873
|
+
for (const [rsId, data] of block.pooled) {
|
|
13874
|
+
if (recovered || fed.length >= block.ssbLength - admitted) break;
|
|
13875
|
+
fed.push(rsId);
|
|
13876
|
+
recovered = decoder.add_repair(blockStart, block.ssbLength, rsId, data, 0);
|
|
13544
13877
|
}
|
|
13545
|
-
|
|
13878
|
+
} catch {
|
|
13879
|
+
recovered = null;
|
|
13880
|
+
} finally {
|
|
13881
|
+
decoder.free?.();
|
|
13882
|
+
}
|
|
13883
|
+
if (recovered && this.#routeRecoveredBlock(blockStart, recovered, ownerPacketId, catalogTrackName, () => {
|
|
13884
|
+
fec.cancelBlockTimeout(blockStart, ownerPacketId, catalogTrackName);
|
|
13885
|
+
fec.countRecovery(ownerPacketId, blockStart, catalogTrackName);
|
|
13886
|
+
fec.markBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
|
|
13887
|
+
})) {
|
|
13888
|
+
this.#unlistedAssistedRecoveries++;
|
|
13889
|
+
this.#assistBlocks.delete(key);
|
|
13890
|
+
return;
|
|
13891
|
+
}
|
|
13892
|
+
for (const rsId of fed) block.pooled.delete(rsId);
|
|
13893
|
+
const failed = (this.#assistFailures.get(key) ?? 0) + 1;
|
|
13894
|
+
this.#assistFailures.delete(key);
|
|
13895
|
+
this.#assistFailures.set(key, failed);
|
|
13896
|
+
for (const oldest of this.#assistFailures.keys()) {
|
|
13897
|
+
if (this.#assistFailures.size <= ASSIST_FAILURE_LEDGER_LIMIT) break;
|
|
13898
|
+
this.#assistFailures.delete(oldest);
|
|
13899
|
+
}
|
|
13900
|
+
}
|
|
13901
|
+
/**
|
|
13902
|
+
* The admission that makes this router protected under `flow` (7.2.7):
|
|
13903
|
+
* its FEC instances discard what they hold of the flow's listed tracks so
|
|
13904
|
+
* no unadmitted symbol stands for an admitted one, then notify. Before it
|
|
13905
|
+
* nothing of the flow was admitted, so every such symbol is unadmitted.
|
|
13906
|
+
* The playback parse ledger is kept: it records MoQ copies already played,
|
|
13907
|
+
* not holdings, and clearing it re-emits that media (audio twice).
|
|
13908
|
+
* The FEC reset is the whole 7.2.7 discard here: no keyframe-overlay
|
|
13909
|
+
* instance exists to hold anything (repair-layers.ts is the catalog
|
|
13910
|
+
* contract only, no decoder), and packet_id 0 is never a listed track
|
|
13911
|
+
* (`ingressHasPacketId`), so nothing of it is held for the flow.
|
|
13912
|
+
*/
|
|
13913
|
+
#noteFlowAdmitted(flow) {
|
|
13914
|
+
const entry = this.#provenance.get(flow);
|
|
13915
|
+
if (!entry || entry.protected) return;
|
|
13916
|
+
entry.protected = true;
|
|
13917
|
+
for (const { name, packetId } of entry.ingress.tracks) {
|
|
13918
|
+
if (name) this.#fecManager?.resetTrack?.(packetId, name);
|
|
13919
|
+
}
|
|
13920
|
+
this.#onFlowProtected?.(flow);
|
|
13921
|
+
}
|
|
13922
|
+
/**
|
|
13923
|
+
* Admit a MoQ datagram of `packetId` on the unicast path of the protected
|
|
13924
|
+
* flow that lists it (7.2.7). `unlisted`: no protected flow lists it, so it
|
|
13925
|
+
* is read as before. A held datagram is routed by `replay` when a record
|
|
13926
|
+
* admits it; one more than one protected flow lists is dropped. So is an
|
|
13927
|
+
* overlap copy: it never enters FEC accounting, so admitting it would leave
|
|
13928
|
+
* its multicast twin a duplicate and the block one symbol short.
|
|
13929
|
+
*/
|
|
13930
|
+
#admitUnicast(packetId, packet, replay, overlapCopy = false) {
|
|
13931
|
+
let receiver;
|
|
13932
|
+
for (const entry of this.#provenance.values()) {
|
|
13933
|
+
if (!entry.protected || !ingressHasPacketId(entry.ingress, packetId)) continue;
|
|
13934
|
+
if (receiver || overlapCopy) {
|
|
13935
|
+
this.#unadmittedListedDrops++;
|
|
13936
|
+
return "dropped";
|
|
13937
|
+
}
|
|
13938
|
+
receiver = entry.receiver;
|
|
13939
|
+
}
|
|
13940
|
+
if (!receiver) return "unlisted";
|
|
13941
|
+
const { verdict } = receiver.onDatagram(packet, "unicast");
|
|
13942
|
+
if (verdict === "admitted") return "admitted";
|
|
13943
|
+
if (verdict === "held") {
|
|
13944
|
+
this.#heldReplay.set(packet, replay);
|
|
13945
|
+
this.#heldAt.set(packet, performance.now());
|
|
13946
|
+
return "held";
|
|
13947
|
+
}
|
|
13948
|
+
this.#unadmittedListedDrops++;
|
|
13949
|
+
return "dropped";
|
|
13950
|
+
}
|
|
13951
|
+
/**
|
|
13952
|
+
* Feed one bc-provenance record of `flow`: a datagram from `manifestPort`
|
|
13953
|
+
* (`multicast`) or an object of the manifest track (`mirror`). Held
|
|
13954
|
+
* datagrams the record admits are routed before this returns.
|
|
13955
|
+
*/
|
|
13956
|
+
feedProvenanceRecord(flow, record, path) {
|
|
13957
|
+
const entry = this.#provenance.get(flow);
|
|
13958
|
+
if (!entry || this.#disposed) return "unregistered";
|
|
13959
|
+
const result = entry.receiver.onRecord(record, path);
|
|
13960
|
+
if (result.admitted.length > 0) {
|
|
13961
|
+
this.#noteFlowAdmitted(flow);
|
|
13962
|
+
this.#withDeferredFecPokes(() => {
|
|
13963
|
+
for (const datagram of result.admitted) {
|
|
13964
|
+
const route = () => {
|
|
13965
|
+
const replay = this.#heldReplay.get(datagram);
|
|
13966
|
+
if (replay) replay();
|
|
13967
|
+
else this.#processMMTP(datagram, "multicast", true, this.#heldIngress.get(datagram) ?? entry.ingress, false, true);
|
|
13968
|
+
};
|
|
13969
|
+
const heldAt = this.#heldAt.get(datagram);
|
|
13970
|
+
if (heldAt !== void 0 && this.#fecManager?.withReceiptTime) this.#fecManager.withReceiptTime(heldAt, route);
|
|
13971
|
+
else route();
|
|
13972
|
+
}
|
|
13973
|
+
});
|
|
13974
|
+
}
|
|
13975
|
+
return result.verdict;
|
|
13976
|
+
}
|
|
13977
|
+
/** The receiver ceased to hold the flow: its hold queues are dropped, not expired. */
|
|
13978
|
+
releaseProvenanceFlow(flow) {
|
|
13979
|
+
this.#provenance.get(flow)?.receiver.release();
|
|
13980
|
+
this.#provenance.delete(flow);
|
|
13981
|
+
for (const byName of this.#provenanceStreamFlow.values()) {
|
|
13982
|
+
for (const [name, bound] of byName) if (bound === flow) byName.delete(name);
|
|
13546
13983
|
}
|
|
13547
13984
|
}
|
|
13985
|
+
/** Multicast datagrams dropped because their ingress names a flow with no registered receiver. */
|
|
13986
|
+
get unregisteredFlowDropCount() {
|
|
13987
|
+
return this.#unregisteredFlowDrops;
|
|
13988
|
+
}
|
|
13989
|
+
/** Recovered packets dropped because more than one protected flow could own their stream. */
|
|
13990
|
+
get ambiguousRecoveredDropCount() {
|
|
13991
|
+
return this.#ambiguousRecoveredDrops;
|
|
13992
|
+
}
|
|
13548
13993
|
/**
|
|
13549
13994
|
* Main entry point: feed a raw MMTP packet for processing.
|
|
13550
13995
|
*
|
|
@@ -13559,10 +14004,10 @@ var MmtpRouter = class {
|
|
|
13559
14004
|
* they may never have crossed — and that attribution is what the physical-
|
|
13560
14005
|
* delivery evidence path is built on. Absence is not multicast; say which.
|
|
13561
14006
|
*/
|
|
13562
|
-
feedPacket(packet, source,
|
|
14007
|
+
feedPacket(packet, source, ingress) {
|
|
13563
14008
|
if (this.#disposed) return;
|
|
13564
14009
|
this.#withDeferredFecPokes(() => {
|
|
13565
|
-
this.#processMMTP(packet, source,
|
|
14010
|
+
this.#processMMTP(packet, source, false, ingress);
|
|
13566
14011
|
});
|
|
13567
14012
|
}
|
|
13568
14013
|
/**
|
|
@@ -13597,7 +14042,7 @@ var MmtpRouter = class {
|
|
|
13597
14042
|
feedOverlapCopy(packet, ingress) {
|
|
13598
14043
|
if (this.#disposed) return;
|
|
13599
14044
|
this.#withDeferredFecPokes(() => {
|
|
13600
|
-
this.#processMMTP(packet, "moq",
|
|
14045
|
+
this.#processMMTP(packet, "moq", false, ingress, true);
|
|
13601
14046
|
});
|
|
13602
14047
|
}
|
|
13603
14048
|
/**
|
|
@@ -13610,7 +14055,7 @@ var MmtpRouter = class {
|
|
|
13610
14055
|
this.#feedRepairPacket(packet, ownerPacketIdHint, catalogTrackName, repairTrackName);
|
|
13611
14056
|
});
|
|
13612
14057
|
}
|
|
13613
|
-
#feedRepairPacket(packet, ownerPacketIdHint, catalogTrackName, repairTrackName) {
|
|
14058
|
+
#feedRepairPacket(packet, ownerPacketIdHint, catalogTrackName, repairTrackName, admitted = false) {
|
|
13614
14059
|
let header;
|
|
13615
14060
|
try {
|
|
13616
14061
|
header = parseMMTPHeader(packet);
|
|
@@ -13627,6 +14072,11 @@ var MmtpRouter = class {
|
|
|
13627
14072
|
`[MmtpRouter] Dropping MoQ repair packet pid=${repairPacketId}: owner hint is required`
|
|
13628
14073
|
);
|
|
13629
14074
|
}
|
|
14075
|
+
if (ownerPacketId != null && !admitted) {
|
|
14076
|
+
const admit = this.#admitUnicast(repairPacketId, packet, () => this.#feedRepairPacket(packet, ownerPacketIdHint, catalogTrackName, repairTrackName, true));
|
|
14077
|
+
if (admit === "held" || admit === "dropped") return;
|
|
14078
|
+
if (admit === "unlisted" && this.#fecManager && this.#setAsideUnlistedRepair(ownerPacketId, catalogTrackName, parsed)) return;
|
|
14079
|
+
}
|
|
13630
14080
|
if (ownerPacketId != null && this.#fecManager) {
|
|
13631
14081
|
this.#moqRepairPidCounts[repairPacketId] = (this.#moqRepairPidCounts[repairPacketId] || 0) + 1;
|
|
13632
14082
|
this.#moqRepairOwnerCounts[ownerPacketId] = (this.#moqRepairOwnerCounts[ownerPacketId] || 0) + 1;
|
|
@@ -13745,6 +14195,7 @@ var MmtpRouter = class {
|
|
|
13745
14195
|
const placement = fec.placeIsoRepair(ownerPacketId, catalogTrackName, repair.ssStart, repair.ssbLength);
|
|
13746
14196
|
if (placement.status !== "placed") return;
|
|
13747
14197
|
const blockStart = placement.block.start;
|
|
14198
|
+
this.#openAssistBlock(ownerPacketId, catalogTrackName, blockStart, repair.ssbLength);
|
|
13748
14199
|
if (placement.span !== void 0) {
|
|
13749
14200
|
this.#replayIsoSources(ownerPacketId, catalogTrackName, placement.span.block.start, placement.span.placed);
|
|
13750
14201
|
}
|
|
@@ -13770,10 +14221,13 @@ var MmtpRouter = class {
|
|
|
13770
14221
|
catalogTrackName
|
|
13771
14222
|
);
|
|
13772
14223
|
if (recovered && !alreadyCompleted) {
|
|
13773
|
-
|
|
13774
|
-
|
|
13775
|
-
|
|
13776
|
-
|
|
14224
|
+
this.#routeRecoveredBlock(blockStart, recovered, ownerPacketId, catalogTrackName, () => {
|
|
14225
|
+
fec.cancelBlockTimeout(blockStart, ownerPacketId, catalogTrackName);
|
|
14226
|
+
fec.countRecovery(ownerPacketId, blockStart, catalogTrackName);
|
|
14227
|
+
fec.markBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
|
|
14228
|
+
});
|
|
14229
|
+
} else if (!recovered) {
|
|
14230
|
+
this.#noteAssistAdmitted(ownerPacketId, catalogTrackName, blockStart, { rsId: repair.rsId }, repair.symbolData);
|
|
13777
14231
|
}
|
|
13778
14232
|
}
|
|
13779
14233
|
/**
|
|
@@ -13813,11 +14267,13 @@ var MmtpRouter = class {
|
|
|
13813
14267
|
if (payload.oversized) continue;
|
|
13814
14268
|
const alreadyCompleted = fec.isBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
|
|
13815
14269
|
const recovered = fec.feedSourceSymbol(ownerPacketId, ssId, payload.symbolData, catalogTrackName);
|
|
14270
|
+
if (!recovered) this.#noteAssistAdmitted(ownerPacketId, catalogTrackName, blockStart, { esi }, payload.symbolData);
|
|
13816
14271
|
if (!recovered || alreadyCompleted) continue;
|
|
13817
|
-
|
|
13818
|
-
|
|
13819
|
-
|
|
13820
|
-
|
|
14272
|
+
this.#routeRecoveredBlock(blockStart, recovered, ownerPacketId, catalogTrackName, () => {
|
|
14273
|
+
fec.cancelBlockTimeout(blockStart, ownerPacketId, catalogTrackName);
|
|
14274
|
+
fec.countRecovery(ownerPacketId, blockStart, catalogTrackName);
|
|
14275
|
+
fec.markBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
|
|
14276
|
+
});
|
|
13821
14277
|
}
|
|
13822
14278
|
}
|
|
13823
14279
|
#resolveRepairOwnerPacketId(packetId, ingress) {
|
|
@@ -13840,14 +14296,18 @@ var MmtpRouter = class {
|
|
|
13840
14296
|
* Sync WASM decoders return the block's K' = SSB_length fixed-width
|
|
13841
14297
|
* ssbg_mode1 source symbols concatenated. */
|
|
13842
14298
|
routeRecoveredBlock(blockId, data, packetId, catalogTrackName) {
|
|
13843
|
-
|
|
14299
|
+
let recovered = false;
|
|
14300
|
+
this.#withDeferredFecPokes(() => {
|
|
14301
|
+
recovered = this.#routeRecoveredBlock(blockId, data, packetId, catalogTrackName);
|
|
14302
|
+
});
|
|
14303
|
+
return recovered;
|
|
13844
14304
|
}
|
|
13845
14305
|
/** Route a single recovered source symbol through the parser + dedup pipeline.
|
|
13846
14306
|
* Worker-backed decoders emit one padded source symbol per frame event. */
|
|
13847
14307
|
routeRecoveredSymbol(blockId, data, packetId, esi = 0, catalogTrackName) {
|
|
13848
14308
|
const blockProfile = this.#blockProfileFor(packetId);
|
|
13849
14309
|
const result = processFecSymbol(data, { ownerPacketId: packetId, index: esi, blockProfile });
|
|
13850
|
-
this.#routeVerifiedFecSymbols(blockId, result, packetId, catalogTrackName);
|
|
14310
|
+
return this.#routeVerifiedFecSymbols(blockId, result, packetId, catalogTrackName);
|
|
13851
14311
|
}
|
|
13852
14312
|
// ── Keyframe timer cancellation ─────────────────────────────────
|
|
13853
14313
|
/** Cancel the active keyframe loss timer.
|
|
@@ -13878,6 +14338,9 @@ var MmtpRouter = class {
|
|
|
13878
14338
|
}
|
|
13879
14339
|
}
|
|
13880
14340
|
this.#parser.resetTrack(packetId, catalogTrackName);
|
|
14341
|
+
const assistPrefix = assistKey(packetId, catalogTrackName, 0).slice(0, -1);
|
|
14342
|
+
for (const key of this.#assistBlocks.keys()) if (key.startsWith(assistPrefix)) this.#assistBlocks.delete(key);
|
|
14343
|
+
for (const key of this.#assistFailures.keys()) if (key.startsWith(assistPrefix)) this.#assistFailures.delete(key);
|
|
13881
14344
|
const streamKey = this.#mediaStreamKey(packetId, catalogTrackName);
|
|
13882
14345
|
this.#trackSynced.delete(streamKey);
|
|
13883
14346
|
this.#droppedPreSyncVideo.delete(streamKey);
|
|
@@ -13890,7 +14353,6 @@ var MmtpRouter = class {
|
|
|
13890
14353
|
this.#lastDirectSourceAudioCursorByStream.delete(streamKey);
|
|
13891
14354
|
this.#lastDirectEmittedAudioCursorByStream.delete(streamKey);
|
|
13892
14355
|
this.#pendingDirectRecoveredAudioByStream.delete(streamKey);
|
|
13893
|
-
this.#manifestRecoveryQueues.delete(streamKey);
|
|
13894
14356
|
this.#deliveredVideoHighWaterByStream.delete(streamKey);
|
|
13895
14357
|
this.#stream(packetId, catalogTrackName).duplicates.clear();
|
|
13896
14358
|
const assemblersToReset = /* @__PURE__ */ new Set();
|
|
@@ -13940,7 +14402,6 @@ var MmtpRouter = class {
|
|
|
13940
14402
|
this.#lastDirectSourceAudioCursorByStream.delete(key);
|
|
13941
14403
|
this.#lastDirectEmittedAudioCursorByStream.delete(key);
|
|
13942
14404
|
this.#pendingDirectRecoveredAudioByStream.delete(key);
|
|
13943
|
-
this.#manifestRecoveryQueues.delete(key);
|
|
13944
14405
|
this.#trackInitDelivered.delete(key);
|
|
13945
14406
|
this.#parserDiagnostics.delete(key);
|
|
13946
14407
|
this.#pendingFecPokes.delete(key);
|
|
@@ -14201,8 +14662,9 @@ var MmtpRouter = class {
|
|
|
14201
14662
|
this.#parserDiagnostics.clear();
|
|
14202
14663
|
this.#catalogTrackNamesByPacketId.clear();
|
|
14203
14664
|
this.#repairOwnersByPacketId.clear();
|
|
14204
|
-
|
|
14205
|
-
this.#
|
|
14665
|
+
for (const { receiver } of this.#provenance.values()) receiver.release();
|
|
14666
|
+
this.#provenance.clear();
|
|
14667
|
+
this.#provenanceStreamFlow.clear();
|
|
14206
14668
|
for (const assembler of this.#mseAssemblers.values()) assembler.dispose();
|
|
14207
14669
|
this.#mseAssemblers.clear();
|
|
14208
14670
|
}
|
|
@@ -14296,6 +14758,24 @@ var MmtpRouter = class {
|
|
|
14296
14758
|
fecReleaseEarliestHeldAtMs(packetId, catalogTrackName) {
|
|
14297
14759
|
return this.#parser.fecReleaseEarliestHeldAtMs(packetId, catalogTrackName);
|
|
14298
14760
|
}
|
|
14761
|
+
/**
|
|
14762
|
+
* Whether a complete RAP above SS_ID `ssId` entered this stream's FEC
|
|
14763
|
+
* release gate (see FecReleaseSequencer.rapAbove).
|
|
14764
|
+
*/
|
|
14765
|
+
fecReleaseRapAbove(packetId, catalogTrackName, ssId) {
|
|
14766
|
+
return this.#parser.fecReleaseRapAbove(packetId, catalogTrackName, ssId);
|
|
14767
|
+
}
|
|
14768
|
+
/**
|
|
14769
|
+
* Observe each complete RAP entering an FEC release gate, with the SS_ID
|
|
14770
|
+
* its frame completed in. Called synchronously from the frame push: an
|
|
14771
|
+
* observer that drives FEC state must defer that work.
|
|
14772
|
+
*/
|
|
14773
|
+
addFecRapObserver(observer) {
|
|
14774
|
+
this.#fecRapObservers.add(observer);
|
|
14775
|
+
return () => {
|
|
14776
|
+
this.#fecRapObservers.delete(observer);
|
|
14777
|
+
};
|
|
14778
|
+
}
|
|
14299
14779
|
#requestFecPoke(packetId, catalogTrackName) {
|
|
14300
14780
|
if (this.#disposed) return;
|
|
14301
14781
|
if (this.#fecPokeDepth > 0) {
|
|
@@ -14341,6 +14821,10 @@ var MmtpRouter = class {
|
|
|
14341
14821
|
this.#onFrameGap?.(decision);
|
|
14342
14822
|
return decision;
|
|
14343
14823
|
},
|
|
14824
|
+
isRap: (data, meta) => this.#isRapForGap(data, meta),
|
|
14825
|
+
onRap: (packetId, catalogTrackName, ssId) => {
|
|
14826
|
+
for (const observer of this.#fecRapObservers) observer(packetId, catalogTrackName, ssId);
|
|
14827
|
+
},
|
|
14344
14828
|
onReleased: (data, meta) => {
|
|
14345
14829
|
if (meta.trackType !== "video" || meta.mpuSequenceNumber === void 0) return;
|
|
14346
14830
|
const timing = meta.authoredTiming;
|
|
@@ -14479,7 +14963,7 @@ var MmtpRouter = class {
|
|
|
14479
14963
|
* 1. parseHeader() → extract packetId + fecPayloadId for FEC feeding
|
|
14480
14964
|
* 2. feed() → MFU extraction, reassembly, keyframe detection, emit via callbacks
|
|
14481
14965
|
*/
|
|
14482
|
-
#processMMTP(packet, source,
|
|
14966
|
+
#processMMTP(packet, source, manifestAdmitted = false, ingress, overlapCopy = false, releasedFromQuarantine = false) {
|
|
14483
14967
|
const parsed = this.#parser.parsePacket(packet);
|
|
14484
14968
|
if (!parsed) return;
|
|
14485
14969
|
const info = parsed.info;
|
|
@@ -14504,43 +14988,32 @@ var MmtpRouter = class {
|
|
|
14504
14988
|
}
|
|
14505
14989
|
const fecUnreadable = !info.isRepair && info.fecPayloadId != null && this.#fecManager !== null && this.#classifyPacketId(info.packetId) !== null && this.#configuredBlockProfile(info.packetId) === void 0;
|
|
14506
14990
|
if (fecUnreadable && !manifestAdmitted) this.#noteUnreadableFecPayloadId(info.packetId, "source");
|
|
14507
|
-
if (!info.isRepair &&
|
|
14508
|
-
let
|
|
14509
|
-
if (!
|
|
14510
|
-
|
|
14511
|
-
|
|
14512
|
-
|
|
14513
|
-
|
|
14514
|
-
|
|
14515
|
-
|
|
14516
|
-
|
|
14517
|
-
recovered: false,
|
|
14518
|
-
sourceSymbolFormat: "full-mmtp-packet"
|
|
14519
|
-
}) : this.#resolveManifestIdentity(
|
|
14520
|
-
info.packetId,
|
|
14521
|
-
coordinates.sbn,
|
|
14522
|
-
coordinates.esi,
|
|
14523
|
-
catalogTrackName
|
|
14524
|
-
)) ?? void 0;
|
|
14525
|
-
}
|
|
14526
|
-
if (identity) {
|
|
14527
|
-
identity = {
|
|
14528
|
-
...identity,
|
|
14529
|
-
packetId: info.packetId,
|
|
14530
|
-
...info.fecPayloadId ? { ssId: info.fecPayloadId.ssId } : {}
|
|
14531
|
-
};
|
|
14532
|
-
const authenticated = info.fecPayloadId ? packet.subarray(0, packet.length - 4) : packet;
|
|
14533
|
-
let quarantined = false;
|
|
14534
|
-
const admission = this.#manifestVerifier.admitObject(identity, authenticated, () => {
|
|
14535
|
-
if (!this.#disposed) {
|
|
14536
|
-
this.#processMMTP(packet, source, identity, true, ingress, overlapCopy, quarantined);
|
|
14537
|
-
}
|
|
14538
|
-
});
|
|
14539
|
-
quarantined = admission.action === "stall";
|
|
14991
|
+
if (source === "multicast" && manifestAdmitted && !info.isRepair && ingress?.flow !== void 0) {
|
|
14992
|
+
let byName = this.#provenanceStreamFlow.get(info.packetId);
|
|
14993
|
+
if (!byName) this.#provenanceStreamFlow.set(info.packetId, byName = /* @__PURE__ */ new Map());
|
|
14994
|
+
byName.set(catalogTrackNameForPacket(ingress, info.packetId), ingress.flow);
|
|
14995
|
+
}
|
|
14996
|
+
if (source === "multicast" && !manifestAdmitted && ingress?.flow !== void 0) {
|
|
14997
|
+
const protectedFlow = this.#provenance.get(ingress.flow);
|
|
14998
|
+
if (!protectedFlow) {
|
|
14999
|
+
this.#unregisteredFlowDrops++;
|
|
15000
|
+
debugRouter(`[MmtpRouter] Dropping multicast packet pid=${info.packetId}: flow ${ingress.flow} has no receiver`);
|
|
14540
15001
|
return;
|
|
14541
15002
|
}
|
|
14542
|
-
|
|
14543
|
-
|
|
15003
|
+
const { verdict } = protectedFlow.receiver.onDatagram(packet, "multicast");
|
|
15004
|
+
if (verdict === "admitted") {
|
|
15005
|
+
this.#noteFlowAdmitted(ingress.flow);
|
|
15006
|
+
this.#processMMTP(packet, source, true, ingress, overlapCopy);
|
|
15007
|
+
} else if (verdict === "held") {
|
|
15008
|
+
this.#heldIngress.set(packet, ingress);
|
|
15009
|
+
this.#heldAt.set(packet, performance.now());
|
|
15010
|
+
}
|
|
15011
|
+
return;
|
|
15012
|
+
}
|
|
15013
|
+
if (source === "moq" && !manifestAdmitted) {
|
|
15014
|
+
const admit = this.#admitUnicast(info.packetId, packet, () => this.#processMMTP(packet, source, true, ingress, overlapCopy, true), overlapCopy);
|
|
15015
|
+
if (admit !== "unlisted") {
|
|
15016
|
+
if (admit === "admitted") this.#processMMTP(packet, source, true, ingress, overlapCopy);
|
|
14544
15017
|
return;
|
|
14545
15018
|
}
|
|
14546
15019
|
}
|
|
@@ -14550,6 +15023,7 @@ var MmtpRouter = class {
|
|
|
14550
15023
|
const ownerPacketId = this.#resolveRepairOwnerPacketId(info.packetId, ingress);
|
|
14551
15024
|
if (ownerPacketId != null && this.#fecManager) {
|
|
14552
15025
|
if (hasAmbiguousCatalogTrackName(ingress, ownerPacketId)) return;
|
|
15026
|
+
if (source === "moq" && !manifestAdmitted && this.#setAsideUnlistedRepair(ownerPacketId, catalogTrackNameForPacket(ingress, ownerPacketId), repair)) return;
|
|
14553
15027
|
this.#repairPidCounts[info.packetId] = (this.#repairPidCounts[info.packetId] || 0) + 1;
|
|
14554
15028
|
const count = this.#repairPidCounts[info.packetId];
|
|
14555
15029
|
if (count <= 2 || count % 500 === 0) {
|
|
@@ -14717,36 +15191,31 @@ var MmtpRouter = class {
|
|
|
14717
15191
|
sourceFec.symbolData,
|
|
14718
15192
|
catalogTrackName
|
|
14719
15193
|
);
|
|
14720
|
-
if (recovered) {
|
|
14721
|
-
|
|
15194
|
+
if (!recovered) {
|
|
15195
|
+
this.#noteAssistAdmitted(
|
|
15196
|
+
info.packetId,
|
|
15197
|
+
catalogTrackName,
|
|
15198
|
+
sourceFec.sbn,
|
|
15199
|
+
{ esi: serial32Distance(sourceFec.sbn, sourceFec.ssId) },
|
|
15200
|
+
sourceFec.symbolData
|
|
15201
|
+
);
|
|
15202
|
+
}
|
|
15203
|
+
if (recovered && !alreadyCompleted) {
|
|
15204
|
+
const fec = this.#fecManager;
|
|
15205
|
+
const sbn = sourceFec.sbn;
|
|
15206
|
+
const packetId = info.packetId;
|
|
15207
|
+
const tracked = this.#classifyPacketId(packetId) !== null;
|
|
15208
|
+
this.#routeRecoveredBlock(sbn, recovered, packetId, catalogTrackName, () => {
|
|
14722
15209
|
if (catalogTrackName === void 0) {
|
|
14723
|
-
|
|
15210
|
+
if (tracked) fec.cancelBlockTimeout(sbn, packetId);
|
|
15211
|
+
fec.countRecovery(packetId, sbn, catalogTrackName);
|
|
15212
|
+
fec.markBlockCompleted(sbn, packetId);
|
|
14724
15213
|
} else {
|
|
14725
|
-
|
|
14726
|
-
|
|
14727
|
-
|
|
14728
|
-
catalogTrackName
|
|
14729
|
-
);
|
|
15214
|
+
if (tracked) fec.cancelBlockTimeout(sbn, packetId, catalogTrackName);
|
|
15215
|
+
fec.countRecovery(packetId, sbn, catalogTrackName);
|
|
15216
|
+
fec.markBlockCompleted(sbn, packetId, catalogTrackName);
|
|
14730
15217
|
}
|
|
14731
|
-
}
|
|
14732
|
-
if (!alreadyCompleted) {
|
|
14733
|
-
this.#fecManager.countRecovery(info.packetId, sourceFec.sbn, catalogTrackName);
|
|
14734
|
-
if (catalogTrackName === void 0) {
|
|
14735
|
-
this.#fecManager.markBlockCompleted(sourceFec.sbn, info.packetId);
|
|
14736
|
-
} else {
|
|
14737
|
-
this.#fecManager.markBlockCompleted(
|
|
14738
|
-
sourceFec.sbn,
|
|
14739
|
-
info.packetId,
|
|
14740
|
-
catalogTrackName
|
|
14741
|
-
);
|
|
14742
|
-
}
|
|
14743
|
-
this.#routeRecoveredBlock(
|
|
14744
|
-
sourceFec.sbn,
|
|
14745
|
-
recovered,
|
|
14746
|
-
info.packetId,
|
|
14747
|
-
catalogTrackName
|
|
14748
|
-
);
|
|
14749
|
-
}
|
|
15218
|
+
});
|
|
14750
15219
|
}
|
|
14751
15220
|
}
|
|
14752
15221
|
}
|
|
@@ -15122,7 +15591,7 @@ var MmtpRouter = class {
|
|
|
15122
15591
|
}
|
|
15123
15592
|
// ── Private: FEC recovery routing ───────────────────────────────
|
|
15124
15593
|
/** Route WASM-recovered block through appropriate path based on container kind. */
|
|
15125
|
-
#routeRecoveredBlock(blockId, recovered, ownerPacketId, catalogTrackName) {
|
|
15594
|
+
#routeRecoveredBlock(blockId, recovered, ownerPacketId, catalogTrackName, complete) {
|
|
15126
15595
|
const packetSymbolSize = this.#symbolSizeForPacket(ownerPacketId);
|
|
15127
15596
|
if (ownerPacketId === void 0) {
|
|
15128
15597
|
throw new Error("[MmtpRouter] a recovered FEC block needs its owner packetId to read its fec.blockProfile");
|
|
@@ -15139,103 +15608,68 @@ var MmtpRouter = class {
|
|
|
15139
15608
|
ownerPacketId,
|
|
15140
15609
|
blockProfile
|
|
15141
15610
|
});
|
|
15142
|
-
this.#routeVerifiedFecSymbols(blockId, result, ownerPacketId, catalogTrackName);
|
|
15611
|
+
return this.#routeVerifiedFecSymbols(blockId, result, ownerPacketId, catalogTrackName, complete);
|
|
15143
15612
|
}
|
|
15144
|
-
|
|
15145
|
-
|
|
15146
|
-
|
|
15147
|
-
|
|
15148
|
-
|
|
15149
|
-
|
|
15150
|
-
|
|
15151
|
-
|
|
15152
|
-
|
|
15613
|
+
/**
|
|
15614
|
+
* A recovered source packet of a protected flow is admitted on its own
|
|
15615
|
+
* digest: its bytes with the SS_ID it was sent with (SS_Start + ESI)
|
|
15616
|
+
* appended are the datagram the record listed (7.2.7). One that no record
|
|
15617
|
+
* lists is dropped; holding it would let it out later as a multicast arrival.
|
|
15618
|
+
* Returns whether the decode recovered the block: every packet it yields
|
|
15619
|
+
* that this router has not parsed already is admitted. A block one of whose
|
|
15620
|
+
* recovered packets is refused is unrecovered (7.2.7): its caller leaves it
|
|
15621
|
+
* open, so it reaches its FEC deadline and fails by class. Always true
|
|
15622
|
+
* outside a protected flow.
|
|
15623
|
+
*/
|
|
15624
|
+
#routeVerifiedFecSymbols(blockId, result, ownerPacketId, catalogTrackName, complete) {
|
|
15625
|
+
const receiver = ownerPacketId == null ? void 0 : this.#provenanceFor(ownerPacketId, catalogTrackName);
|
|
15626
|
+
let recovered = true;
|
|
15627
|
+
if (receiver) {
|
|
15628
|
+
const parsed = this.#stream(ownerPacketId, catalogTrackName).duplicates.parsedSymbols;
|
|
15629
|
+
result = {
|
|
15630
|
+
...result,
|
|
15631
|
+
symbols: result.symbols.filter((sym) => {
|
|
15632
|
+
const ssId = this.#fecAnchorForRecovered(blockId, sym.index);
|
|
15633
|
+
if (parsed.has(ssId)) return false;
|
|
15634
|
+
let admitted = false;
|
|
15635
|
+
if (receiver === "ambiguous") {
|
|
15636
|
+
this.#ambiguousRecoveredDrops++;
|
|
15637
|
+
} else {
|
|
15638
|
+
const wire = new Uint8Array(sym.rawSymbol.byteLength + 4);
|
|
15639
|
+
wire.set(sym.rawSymbol);
|
|
15640
|
+
new DataView(wire.buffer).setUint32(sym.rawSymbol.byteLength, ssId);
|
|
15641
|
+
admitted = receiver.admitRecovered(wire);
|
|
15642
|
+
}
|
|
15643
|
+
if (!admitted) recovered = false;
|
|
15644
|
+
return admitted;
|
|
15645
|
+
})
|
|
15646
|
+
};
|
|
15153
15647
|
}
|
|
15154
|
-
|
|
15155
|
-
|
|
15156
|
-
|
|
15157
|
-
|
|
15158
|
-
|
|
15159
|
-
|
|
15160
|
-
|
|
15161
|
-
|
|
15162
|
-
|
|
15163
|
-
|
|
15648
|
+
if (recovered) complete?.();
|
|
15649
|
+
this.#routeProcessedFecSymbols(blockId, result, ownerPacketId, catalogTrackName);
|
|
15650
|
+
return recovered;
|
|
15651
|
+
}
|
|
15652
|
+
/**
|
|
15653
|
+
* The receiver a recovered packet of `packetId` answers to: the flow that
|
|
15654
|
+
* admitted the stream's source datagrams. Before any was admitted, the one
|
|
15655
|
+
* flow whose registration lists the stream; more than one (a repair flow
|
|
15656
|
+
* also lists its owners' source tracks) cannot be told apart, so none.
|
|
15657
|
+
*/
|
|
15658
|
+
#provenanceFor(packetId, catalogTrackName) {
|
|
15659
|
+
const byName = this.#provenanceStreamFlow.get(packetId);
|
|
15660
|
+
const flows = new Set(
|
|
15661
|
+
catalogTrackName === void 0 || !byName ? byName?.values() : byName.has(catalogTrackName) ? [byName.get(catalogTrackName)] : []
|
|
15662
|
+
);
|
|
15663
|
+
if (flows.size > 1) return "ambiguous";
|
|
15664
|
+
for (const bound of flows) return this.#provenance.get(bound)?.receiver ?? "ambiguous";
|
|
15665
|
+
let match;
|
|
15666
|
+
for (const { receiver, ingress } of this.#provenance.values()) {
|
|
15667
|
+
if (ingress.tracks.some((t) => t.packetId === packetId && (catalogTrackName === void 0 || t.name === catalogTrackName))) {
|
|
15668
|
+
if (match) return "ambiguous";
|
|
15669
|
+
match = receiver;
|
|
15164
15670
|
}
|
|
15165
|
-
this.#admitRecoveredResult(
|
|
15166
|
-
next.blockId,
|
|
15167
|
-
next.result,
|
|
15168
|
-
next.ownerPacketId,
|
|
15169
|
-
next.catalogTrackName,
|
|
15170
|
-
() => {
|
|
15171
|
-
queue.shift();
|
|
15172
|
-
drain();
|
|
15173
|
-
}
|
|
15174
|
-
);
|
|
15175
|
-
};
|
|
15176
|
-
drain();
|
|
15177
|
-
}
|
|
15178
|
-
#admitRecoveredResult(blockId, result, ownerPacketId, catalogTrackName, done) {
|
|
15179
|
-
const verifier = this.#manifestVerifier;
|
|
15180
|
-
const resolveIdentity = this.#resolveManifestIdentity;
|
|
15181
|
-
const resolveIdentityForRoute = this.#resolveManifestIdentityForRoute;
|
|
15182
|
-
if (!verifier) {
|
|
15183
|
-
this.#routeProcessedFecSymbols(blockId, result, ownerPacketId, catalogTrackName);
|
|
15184
|
-
done();
|
|
15185
|
-
return;
|
|
15186
15671
|
}
|
|
15187
|
-
|
|
15188
|
-
const symbols = result.symbols;
|
|
15189
|
-
const admitNext = (index) => {
|
|
15190
|
-
const symbol = symbols[index];
|
|
15191
|
-
if (!symbol) {
|
|
15192
|
-
done();
|
|
15193
|
-
return;
|
|
15194
|
-
}
|
|
15195
|
-
const identity = resolveIdentityForRoute ? resolveIdentityForRoute({
|
|
15196
|
-
packetId: ownerPacketId,
|
|
15197
|
-
sbn: blockId,
|
|
15198
|
-
esi: symbol.index,
|
|
15199
|
-
...catalogTrackName === void 0 ? {} : { catalogTrackName },
|
|
15200
|
-
recovered: true,
|
|
15201
|
-
sourceSymbolFormat: "fec-padded-recovery"
|
|
15202
|
-
}) : resolveIdentity(ownerPacketId, blockId, symbol.index, catalogTrackName);
|
|
15203
|
-
if (!identity) {
|
|
15204
|
-
console.warn(`[MmtpRouter] manifest identity unresolved; recovered symbol is fail-open for packetId=${ownerPacketId} block=${blockId} esi=${symbol.index}`);
|
|
15205
|
-
this.#routeProcessedFecSymbols(
|
|
15206
|
-
blockId,
|
|
15207
|
-
{ ...result, symbols: [symbol] },
|
|
15208
|
-
ownerPacketId,
|
|
15209
|
-
catalogTrackName
|
|
15210
|
-
);
|
|
15211
|
-
admitNext(index + 1);
|
|
15212
|
-
return;
|
|
15213
|
-
}
|
|
15214
|
-
const admission = verifier.admitObject(
|
|
15215
|
-
{
|
|
15216
|
-
...identity,
|
|
15217
|
-
packetId: ownerPacketId
|
|
15218
|
-
},
|
|
15219
|
-
symbol.rawSymbol,
|
|
15220
|
-
() => {
|
|
15221
|
-
if (!this.#disposed) {
|
|
15222
|
-
this.#routeProcessedFecSymbols(
|
|
15223
|
-
blockId,
|
|
15224
|
-
{ ...result, symbols: [symbol] },
|
|
15225
|
-
ownerPacketId,
|
|
15226
|
-
catalogTrackName
|
|
15227
|
-
);
|
|
15228
|
-
}
|
|
15229
|
-
admitNext(index + 1);
|
|
15230
|
-
},
|
|
15231
|
-
// A stalled symbol that is later discarded instead of played must still
|
|
15232
|
-
// advance this loop; enforce mode never calls the release callback for
|
|
15233
|
-
// it (BLO-30095).
|
|
15234
|
-
() => admitNext(index + 1)
|
|
15235
|
-
);
|
|
15236
|
-
if (admission.action === "drop") admitNext(index + 1);
|
|
15237
|
-
};
|
|
15238
|
-
admitNext(0);
|
|
15672
|
+
return match;
|
|
15239
15673
|
}
|
|
15240
15674
|
#routeProcessedFecSymbols(blockId, result, ownerPacketId, catalogTrackName) {
|
|
15241
15675
|
if (ownerPacketId == null) return;
|
|
@@ -16215,14 +16649,22 @@ function toWindowMulticastSubscribeConfig(input) {
|
|
|
16215
16649
|
if (input.packetIds?.some((id) => !isMulticastTrackPacketId(id)) || input.tracks?.some((track) => !isMulticastTrackPacketId(track.packetId))) {
|
|
16216
16650
|
throw new RangeError(`multicast packetId must be an integer from 1 to ${MMTP_PACKET_ID_MAX2}; packetId 0 is the MMTP signaling flow and is never a track's`);
|
|
16217
16651
|
}
|
|
16652
|
+
const source = input.source?.trim() || void 0;
|
|
16653
|
+
const senderAddress = input.senderAddress?.trim() || void 0;
|
|
16654
|
+
if (source && senderAddress) {
|
|
16655
|
+
throw new TypeError("multicast senderAddress is an ASM filter and never appears beside source");
|
|
16656
|
+
}
|
|
16218
16657
|
const base = {
|
|
16219
16658
|
group: input.group,
|
|
16220
16659
|
port: input.port,
|
|
16221
|
-
...
|
|
16660
|
+
...source ? { source } : {},
|
|
16222
16661
|
consume: "bytes",
|
|
16223
16662
|
...input.packetIds?.length ? { packetIds: [...input.packetIds] } : {}
|
|
16224
16663
|
};
|
|
16664
|
+
const filter = senderAddress ? { senderAddress } : {};
|
|
16225
16665
|
if (input.atsc3) {
|
|
16666
|
+
if (senderAddress)
|
|
16667
|
+
throw new TypeError("an ATSC3 tuner request carries no senderAddress");
|
|
16226
16668
|
const { frequency, plpId, serviceId } = input.atsc3;
|
|
16227
16669
|
if (!Number.isInteger(frequency) || frequency < 1) {
|
|
16228
16670
|
throw new TypeError(`ATSC3 tuning requires a positive integer frequency in kHz, got ${String(frequency)}`);
|
|
@@ -16242,6 +16684,7 @@ function toWindowMulticastSubscribeConfig(input) {
|
|
|
16242
16684
|
if (!wantsAmt) {
|
|
16243
16685
|
const native = {
|
|
16244
16686
|
...base,
|
|
16687
|
+
...filter,
|
|
16245
16688
|
origin: "ip-multicast",
|
|
16246
16689
|
...input.tracks?.length ? { tracks: input.tracks.map(({ name, packetId }) => ({ name, packetId })) } : {}
|
|
16247
16690
|
};
|
|
@@ -16267,6 +16710,7 @@ function toWindowMulticastSubscribeConfig(input) {
|
|
|
16267
16710
|
}
|
|
16268
16711
|
return {
|
|
16269
16712
|
...base,
|
|
16713
|
+
...filter,
|
|
16270
16714
|
origin: "amt",
|
|
16271
16715
|
relay,
|
|
16272
16716
|
relayPort
|
|
@@ -16292,6 +16736,9 @@ function isValidMulticastGroupAddress(address) {
|
|
|
16292
16736
|
function normalizeMulticastEndpoint(endpoint) {
|
|
16293
16737
|
const groupAddress = endpoint.groupAddress.trim().toLowerCase();
|
|
16294
16738
|
const sourceAddress = endpoint.sourceAddress?.trim().toLowerCase() || void 0;
|
|
16739
|
+
const senderAddress = endpoint.senderAddress?.trim().toLowerCase() || void 0;
|
|
16740
|
+
if (sourceAddress && senderAddress)
|
|
16741
|
+
return void 0;
|
|
16295
16742
|
const amtRelay = endpoint.amtRelay?.trim().toLowerCase() || void 0;
|
|
16296
16743
|
if (!isValidMulticastGroupAddress(groupAddress) || !isPositivePort(endpoint.port))
|
|
16297
16744
|
return void 0;
|
|
@@ -16302,6 +16749,7 @@ function normalizeMulticastEndpoint(endpoint) {
|
|
|
16302
16749
|
return {
|
|
16303
16750
|
groupAddress,
|
|
16304
16751
|
...sourceAddress ? { sourceAddress } : {},
|
|
16752
|
+
...senderAddress ? { senderAddress } : {},
|
|
16305
16753
|
port: endpoint.port,
|
|
16306
16754
|
...endpoint.mode ? { mode: endpoint.mode } : {},
|
|
16307
16755
|
...amtRelay ? { amtRelay } : {},
|
|
@@ -16321,7 +16769,9 @@ function multicastEndpointKey(endpoint) {
|
|
|
16321
16769
|
normalized.amtRelay ?? "",
|
|
16322
16770
|
normalized.amtRelayPort ?? "",
|
|
16323
16771
|
// A DRIAD join is a different gateway subscription from a native one.
|
|
16324
|
-
...normalized.amtDiscovery ? [normalized.amtDiscovery] : []
|
|
16772
|
+
...normalized.amtDiscovery ? [normalized.amtDiscovery] : [],
|
|
16773
|
+
// An ASM sender filter is a different subscription from the unfiltered join.
|
|
16774
|
+
...normalized.senderAddress ? [{ senderAddress: normalized.senderAddress }] : []
|
|
16325
16775
|
]);
|
|
16326
16776
|
}
|
|
16327
16777
|
function isMulticastTrackPacketId(value) {
|
|
@@ -17257,10 +17707,11 @@ var MulticastTransport = class {
|
|
|
17257
17707
|
try {
|
|
17258
17708
|
const packetIds = config.packetIds?.length ? new Set(config.packetIds) : null;
|
|
17259
17709
|
console.log(
|
|
17260
|
-
`[MulticastTransport] window.multicast.subscribe(${config.sourceAddress}, ${config.groupAddress}, ${config.port})`
|
|
17710
|
+
`[MulticastTransport] window.multicast.subscribe(${config.sourceAddress ?? config.senderAddress ?? "*"}, ${config.groupAddress}, ${config.port})`
|
|
17261
17711
|
);
|
|
17262
17712
|
const subscribeConfig = toWindowMulticastSubscribeConfig({
|
|
17263
17713
|
source: config.sourceAddress,
|
|
17714
|
+
senderAddress: config.senderAddress,
|
|
17264
17715
|
group: config.groupAddress,
|
|
17265
17716
|
port: config.port,
|
|
17266
17717
|
mode: config.mode,
|
|
@@ -17856,6 +18307,2726 @@ var MulticastTransport = class {
|
|
|
17856
18307
|
}
|
|
17857
18308
|
};
|
|
17858
18309
|
|
|
18310
|
+
// ../mmt-manifest-verify/node_modules/@noble/hashes/esm/crypto.js
|
|
18311
|
+
var crypto2 = typeof globalThis === "object" && "crypto" in globalThis ? globalThis.crypto : void 0;
|
|
18312
|
+
|
|
18313
|
+
// ../mmt-manifest-verify/node_modules/@noble/hashes/esm/utils.js
|
|
18314
|
+
function isBytes(a) {
|
|
18315
|
+
return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
|
|
18316
|
+
}
|
|
18317
|
+
function anumber(n) {
|
|
18318
|
+
if (!Number.isSafeInteger(n) || n < 0)
|
|
18319
|
+
throw new Error("positive integer expected, got " + n);
|
|
18320
|
+
}
|
|
18321
|
+
function abytes(b, ...lengths) {
|
|
18322
|
+
if (!isBytes(b))
|
|
18323
|
+
throw new Error("Uint8Array expected");
|
|
18324
|
+
if (lengths.length > 0 && !lengths.includes(b.length))
|
|
18325
|
+
throw new Error("Uint8Array expected of length " + lengths + ", got length=" + b.length);
|
|
18326
|
+
}
|
|
18327
|
+
function aexists(instance, checkFinished = true) {
|
|
18328
|
+
if (instance.destroyed)
|
|
18329
|
+
throw new Error("Hash instance has been destroyed");
|
|
18330
|
+
if (checkFinished && instance.finished)
|
|
18331
|
+
throw new Error("Hash#digest() has already been called");
|
|
18332
|
+
}
|
|
18333
|
+
function aoutput(out, instance) {
|
|
18334
|
+
abytes(out);
|
|
18335
|
+
const min = instance.outputLen;
|
|
18336
|
+
if (out.length < min) {
|
|
18337
|
+
throw new Error("digestInto() expects output buffer of length at least " + min);
|
|
18338
|
+
}
|
|
18339
|
+
}
|
|
18340
|
+
function clean(...arrays) {
|
|
18341
|
+
for (let i = 0; i < arrays.length; i++) {
|
|
18342
|
+
arrays[i].fill(0);
|
|
18343
|
+
}
|
|
18344
|
+
}
|
|
18345
|
+
function createView(arr) {
|
|
18346
|
+
return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
|
|
18347
|
+
}
|
|
18348
|
+
function rotr(word, shift) {
|
|
18349
|
+
return word << 32 - shift | word >>> shift;
|
|
18350
|
+
}
|
|
18351
|
+
var hasHexBuiltin = /* @__PURE__ */ (() => (
|
|
18352
|
+
// @ts-ignore
|
|
18353
|
+
typeof Uint8Array.from([]).toHex === "function" && typeof Uint8Array.fromHex === "function"
|
|
18354
|
+
))();
|
|
18355
|
+
var hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
|
|
18356
|
+
function bytesToHex(bytes) {
|
|
18357
|
+
abytes(bytes);
|
|
18358
|
+
if (hasHexBuiltin)
|
|
18359
|
+
return bytes.toHex();
|
|
18360
|
+
let hex2 = "";
|
|
18361
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
18362
|
+
hex2 += hexes[bytes[i]];
|
|
18363
|
+
}
|
|
18364
|
+
return hex2;
|
|
18365
|
+
}
|
|
18366
|
+
var asciis = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 };
|
|
18367
|
+
function asciiToBase16(ch) {
|
|
18368
|
+
if (ch >= asciis._0 && ch <= asciis._9)
|
|
18369
|
+
return ch - asciis._0;
|
|
18370
|
+
if (ch >= asciis.A && ch <= asciis.F)
|
|
18371
|
+
return ch - (asciis.A - 10);
|
|
18372
|
+
if (ch >= asciis.a && ch <= asciis.f)
|
|
18373
|
+
return ch - (asciis.a - 10);
|
|
18374
|
+
return;
|
|
18375
|
+
}
|
|
18376
|
+
function hexToBytes(hex2) {
|
|
18377
|
+
if (typeof hex2 !== "string")
|
|
18378
|
+
throw new Error("hex string expected, got " + typeof hex2);
|
|
18379
|
+
if (hasHexBuiltin)
|
|
18380
|
+
return Uint8Array.fromHex(hex2);
|
|
18381
|
+
const hl = hex2.length;
|
|
18382
|
+
const al = hl / 2;
|
|
18383
|
+
if (hl % 2)
|
|
18384
|
+
throw new Error("hex string expected, got unpadded hex of length " + hl);
|
|
18385
|
+
const array = new Uint8Array(al);
|
|
18386
|
+
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
|
|
18387
|
+
const n1 = asciiToBase16(hex2.charCodeAt(hi));
|
|
18388
|
+
const n2 = asciiToBase16(hex2.charCodeAt(hi + 1));
|
|
18389
|
+
if (n1 === void 0 || n2 === void 0) {
|
|
18390
|
+
const char = hex2[hi] + hex2[hi + 1];
|
|
18391
|
+
throw new Error('hex string expected, got non-hex character "' + char + '" at index ' + hi);
|
|
18392
|
+
}
|
|
18393
|
+
array[ai] = n1 * 16 + n2;
|
|
18394
|
+
}
|
|
18395
|
+
return array;
|
|
18396
|
+
}
|
|
18397
|
+
function utf8ToBytes(str) {
|
|
18398
|
+
if (typeof str !== "string")
|
|
18399
|
+
throw new Error("string expected");
|
|
18400
|
+
return new Uint8Array(new TextEncoder().encode(str));
|
|
18401
|
+
}
|
|
18402
|
+
function toBytes(data) {
|
|
18403
|
+
if (typeof data === "string")
|
|
18404
|
+
data = utf8ToBytes(data);
|
|
18405
|
+
abytes(data);
|
|
18406
|
+
return data;
|
|
18407
|
+
}
|
|
18408
|
+
function concatBytes(...arrays) {
|
|
18409
|
+
let sum = 0;
|
|
18410
|
+
for (let i = 0; i < arrays.length; i++) {
|
|
18411
|
+
const a = arrays[i];
|
|
18412
|
+
abytes(a);
|
|
18413
|
+
sum += a.length;
|
|
18414
|
+
}
|
|
18415
|
+
const res = new Uint8Array(sum);
|
|
18416
|
+
for (let i = 0, pad = 0; i < arrays.length; i++) {
|
|
18417
|
+
const a = arrays[i];
|
|
18418
|
+
res.set(a, pad);
|
|
18419
|
+
pad += a.length;
|
|
18420
|
+
}
|
|
18421
|
+
return res;
|
|
18422
|
+
}
|
|
18423
|
+
var Hash = class {
|
|
18424
|
+
};
|
|
18425
|
+
function createHasher(hashCons) {
|
|
18426
|
+
const hashC = (msg) => hashCons().update(toBytes(msg)).digest();
|
|
18427
|
+
const tmp = hashCons();
|
|
18428
|
+
hashC.outputLen = tmp.outputLen;
|
|
18429
|
+
hashC.blockLen = tmp.blockLen;
|
|
18430
|
+
hashC.create = () => hashCons();
|
|
18431
|
+
return hashC;
|
|
18432
|
+
}
|
|
18433
|
+
function randomBytes(bytesLength = 32) {
|
|
18434
|
+
if (crypto2 && typeof crypto2.getRandomValues === "function") {
|
|
18435
|
+
return crypto2.getRandomValues(new Uint8Array(bytesLength));
|
|
18436
|
+
}
|
|
18437
|
+
if (crypto2 && typeof crypto2.randomBytes === "function") {
|
|
18438
|
+
return Uint8Array.from(crypto2.randomBytes(bytesLength));
|
|
18439
|
+
}
|
|
18440
|
+
throw new Error("crypto.getRandomValues must be defined");
|
|
18441
|
+
}
|
|
18442
|
+
|
|
18443
|
+
// ../mmt-manifest-verify/node_modules/@noble/hashes/esm/_md.js
|
|
18444
|
+
function setBigUint64(view2, byteOffset, value, isLE) {
|
|
18445
|
+
if (typeof view2.setBigUint64 === "function")
|
|
18446
|
+
return view2.setBigUint64(byteOffset, value, isLE);
|
|
18447
|
+
const _32n2 = BigInt(32);
|
|
18448
|
+
const _u32_max = BigInt(4294967295);
|
|
18449
|
+
const wh = Number(value >> _32n2 & _u32_max);
|
|
18450
|
+
const wl = Number(value & _u32_max);
|
|
18451
|
+
const h = isLE ? 4 : 0;
|
|
18452
|
+
const l = isLE ? 0 : 4;
|
|
18453
|
+
view2.setUint32(byteOffset + h, wh, isLE);
|
|
18454
|
+
view2.setUint32(byteOffset + l, wl, isLE);
|
|
18455
|
+
}
|
|
18456
|
+
function Chi(a, b, c) {
|
|
18457
|
+
return a & b ^ ~a & c;
|
|
18458
|
+
}
|
|
18459
|
+
function Maj(a, b, c) {
|
|
18460
|
+
return a & b ^ a & c ^ b & c;
|
|
18461
|
+
}
|
|
18462
|
+
var HashMD = class extends Hash {
|
|
18463
|
+
constructor(blockLen, outputLen, padOffset, isLE) {
|
|
18464
|
+
super();
|
|
18465
|
+
this.finished = false;
|
|
18466
|
+
this.length = 0;
|
|
18467
|
+
this.pos = 0;
|
|
18468
|
+
this.destroyed = false;
|
|
18469
|
+
this.blockLen = blockLen;
|
|
18470
|
+
this.outputLen = outputLen;
|
|
18471
|
+
this.padOffset = padOffset;
|
|
18472
|
+
this.isLE = isLE;
|
|
18473
|
+
this.buffer = new Uint8Array(blockLen);
|
|
18474
|
+
this.view = createView(this.buffer);
|
|
18475
|
+
}
|
|
18476
|
+
update(data) {
|
|
18477
|
+
aexists(this);
|
|
18478
|
+
data = toBytes(data);
|
|
18479
|
+
abytes(data);
|
|
18480
|
+
const { view: view2, buffer, blockLen } = this;
|
|
18481
|
+
const len = data.length;
|
|
18482
|
+
for (let pos = 0; pos < len; ) {
|
|
18483
|
+
const take = Math.min(blockLen - this.pos, len - pos);
|
|
18484
|
+
if (take === blockLen) {
|
|
18485
|
+
const dataView = createView(data);
|
|
18486
|
+
for (; blockLen <= len - pos; pos += blockLen)
|
|
18487
|
+
this.process(dataView, pos);
|
|
18488
|
+
continue;
|
|
18489
|
+
}
|
|
18490
|
+
buffer.set(data.subarray(pos, pos + take), this.pos);
|
|
18491
|
+
this.pos += take;
|
|
18492
|
+
pos += take;
|
|
18493
|
+
if (this.pos === blockLen) {
|
|
18494
|
+
this.process(view2, 0);
|
|
18495
|
+
this.pos = 0;
|
|
18496
|
+
}
|
|
18497
|
+
}
|
|
18498
|
+
this.length += data.length;
|
|
18499
|
+
this.roundClean();
|
|
18500
|
+
return this;
|
|
18501
|
+
}
|
|
18502
|
+
digestInto(out) {
|
|
18503
|
+
aexists(this);
|
|
18504
|
+
aoutput(out, this);
|
|
18505
|
+
this.finished = true;
|
|
18506
|
+
const { buffer, view: view2, blockLen, isLE } = this;
|
|
18507
|
+
let { pos } = this;
|
|
18508
|
+
buffer[pos++] = 128;
|
|
18509
|
+
clean(this.buffer.subarray(pos));
|
|
18510
|
+
if (this.padOffset > blockLen - pos) {
|
|
18511
|
+
this.process(view2, 0);
|
|
18512
|
+
pos = 0;
|
|
18513
|
+
}
|
|
18514
|
+
for (let i = pos; i < blockLen; i++)
|
|
18515
|
+
buffer[i] = 0;
|
|
18516
|
+
setBigUint64(view2, blockLen - 8, BigInt(this.length * 8), isLE);
|
|
18517
|
+
this.process(view2, 0);
|
|
18518
|
+
const oview = createView(out);
|
|
18519
|
+
const len = this.outputLen;
|
|
18520
|
+
if (len % 4)
|
|
18521
|
+
throw new Error("_sha2: outputLen should be aligned to 32bit");
|
|
18522
|
+
const outLen = len / 4;
|
|
18523
|
+
const state = this.get();
|
|
18524
|
+
if (outLen > state.length)
|
|
18525
|
+
throw new Error("_sha2: outputLen bigger than state");
|
|
18526
|
+
for (let i = 0; i < outLen; i++)
|
|
18527
|
+
oview.setUint32(4 * i, state[i], isLE);
|
|
18528
|
+
}
|
|
18529
|
+
digest() {
|
|
18530
|
+
const { buffer, outputLen } = this;
|
|
18531
|
+
this.digestInto(buffer);
|
|
18532
|
+
const res = buffer.slice(0, outputLen);
|
|
18533
|
+
this.destroy();
|
|
18534
|
+
return res;
|
|
18535
|
+
}
|
|
18536
|
+
_cloneInto(to) {
|
|
18537
|
+
to || (to = new this.constructor());
|
|
18538
|
+
to.set(...this.get());
|
|
18539
|
+
const { blockLen, buffer, length, finished, destroyed, pos } = this;
|
|
18540
|
+
to.destroyed = destroyed;
|
|
18541
|
+
to.finished = finished;
|
|
18542
|
+
to.length = length;
|
|
18543
|
+
to.pos = pos;
|
|
18544
|
+
if (length % blockLen)
|
|
18545
|
+
to.buffer.set(buffer);
|
|
18546
|
+
return to;
|
|
18547
|
+
}
|
|
18548
|
+
clone() {
|
|
18549
|
+
return this._cloneInto();
|
|
18550
|
+
}
|
|
18551
|
+
};
|
|
18552
|
+
var SHA256_IV = /* @__PURE__ */ Uint32Array.from([
|
|
18553
|
+
1779033703,
|
|
18554
|
+
3144134277,
|
|
18555
|
+
1013904242,
|
|
18556
|
+
2773480762,
|
|
18557
|
+
1359893119,
|
|
18558
|
+
2600822924,
|
|
18559
|
+
528734635,
|
|
18560
|
+
1541459225
|
|
18561
|
+
]);
|
|
18562
|
+
var SHA512_IV = /* @__PURE__ */ Uint32Array.from([
|
|
18563
|
+
1779033703,
|
|
18564
|
+
4089235720,
|
|
18565
|
+
3144134277,
|
|
18566
|
+
2227873595,
|
|
18567
|
+
1013904242,
|
|
18568
|
+
4271175723,
|
|
18569
|
+
2773480762,
|
|
18570
|
+
1595750129,
|
|
18571
|
+
1359893119,
|
|
18572
|
+
2917565137,
|
|
18573
|
+
2600822924,
|
|
18574
|
+
725511199,
|
|
18575
|
+
528734635,
|
|
18576
|
+
4215389547,
|
|
18577
|
+
1541459225,
|
|
18578
|
+
327033209
|
|
18579
|
+
]);
|
|
18580
|
+
|
|
18581
|
+
// ../mmt-manifest-verify/node_modules/@noble/hashes/esm/_u64.js
|
|
18582
|
+
var U32_MASK64 = /* @__PURE__ */ BigInt(2 ** 32 - 1);
|
|
18583
|
+
var _32n = /* @__PURE__ */ BigInt(32);
|
|
18584
|
+
function fromBig(n, le = false) {
|
|
18585
|
+
if (le)
|
|
18586
|
+
return { h: Number(n & U32_MASK64), l: Number(n >> _32n & U32_MASK64) };
|
|
18587
|
+
return { h: Number(n >> _32n & U32_MASK64) | 0, l: Number(n & U32_MASK64) | 0 };
|
|
18588
|
+
}
|
|
18589
|
+
function split(lst, le = false) {
|
|
18590
|
+
const len = lst.length;
|
|
18591
|
+
let Ah = new Uint32Array(len);
|
|
18592
|
+
let Al = new Uint32Array(len);
|
|
18593
|
+
for (let i = 0; i < len; i++) {
|
|
18594
|
+
const { h, l } = fromBig(lst[i], le);
|
|
18595
|
+
[Ah[i], Al[i]] = [h, l];
|
|
18596
|
+
}
|
|
18597
|
+
return [Ah, Al];
|
|
18598
|
+
}
|
|
18599
|
+
var shrSH = (h, _l, s) => h >>> s;
|
|
18600
|
+
var shrSL = (h, l, s) => h << 32 - s | l >>> s;
|
|
18601
|
+
var rotrSH = (h, l, s) => h >>> s | l << 32 - s;
|
|
18602
|
+
var rotrSL = (h, l, s) => h << 32 - s | l >>> s;
|
|
18603
|
+
var rotrBH = (h, l, s) => h << 64 - s | l >>> s - 32;
|
|
18604
|
+
var rotrBL = (h, l, s) => h >>> s - 32 | l << 64 - s;
|
|
18605
|
+
function add(Ah, Al, Bh, Bl) {
|
|
18606
|
+
const l = (Al >>> 0) + (Bl >>> 0);
|
|
18607
|
+
return { h: Ah + Bh + (l / 2 ** 32 | 0) | 0, l: l | 0 };
|
|
18608
|
+
}
|
|
18609
|
+
var add3L = (Al, Bl, Cl) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0);
|
|
18610
|
+
var add3H = (low, Ah, Bh, Ch) => Ah + Bh + Ch + (low / 2 ** 32 | 0) | 0;
|
|
18611
|
+
var add4L = (Al, Bl, Cl, Dl) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0) + (Dl >>> 0);
|
|
18612
|
+
var add4H = (low, Ah, Bh, Ch, Dh) => Ah + Bh + Ch + Dh + (low / 2 ** 32 | 0) | 0;
|
|
18613
|
+
var add5L = (Al, Bl, Cl, Dl, El) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0) + (Dl >>> 0) + (El >>> 0);
|
|
18614
|
+
var add5H = (low, Ah, Bh, Ch, Dh, Eh) => Ah + Bh + Ch + Dh + Eh + (low / 2 ** 32 | 0) | 0;
|
|
18615
|
+
|
|
18616
|
+
// ../mmt-manifest-verify/node_modules/@noble/hashes/esm/sha2.js
|
|
18617
|
+
var SHA256_K = /* @__PURE__ */ Uint32Array.from([
|
|
18618
|
+
1116352408,
|
|
18619
|
+
1899447441,
|
|
18620
|
+
3049323471,
|
|
18621
|
+
3921009573,
|
|
18622
|
+
961987163,
|
|
18623
|
+
1508970993,
|
|
18624
|
+
2453635748,
|
|
18625
|
+
2870763221,
|
|
18626
|
+
3624381080,
|
|
18627
|
+
310598401,
|
|
18628
|
+
607225278,
|
|
18629
|
+
1426881987,
|
|
18630
|
+
1925078388,
|
|
18631
|
+
2162078206,
|
|
18632
|
+
2614888103,
|
|
18633
|
+
3248222580,
|
|
18634
|
+
3835390401,
|
|
18635
|
+
4022224774,
|
|
18636
|
+
264347078,
|
|
18637
|
+
604807628,
|
|
18638
|
+
770255983,
|
|
18639
|
+
1249150122,
|
|
18640
|
+
1555081692,
|
|
18641
|
+
1996064986,
|
|
18642
|
+
2554220882,
|
|
18643
|
+
2821834349,
|
|
18644
|
+
2952996808,
|
|
18645
|
+
3210313671,
|
|
18646
|
+
3336571891,
|
|
18647
|
+
3584528711,
|
|
18648
|
+
113926993,
|
|
18649
|
+
338241895,
|
|
18650
|
+
666307205,
|
|
18651
|
+
773529912,
|
|
18652
|
+
1294757372,
|
|
18653
|
+
1396182291,
|
|
18654
|
+
1695183700,
|
|
18655
|
+
1986661051,
|
|
18656
|
+
2177026350,
|
|
18657
|
+
2456956037,
|
|
18658
|
+
2730485921,
|
|
18659
|
+
2820302411,
|
|
18660
|
+
3259730800,
|
|
18661
|
+
3345764771,
|
|
18662
|
+
3516065817,
|
|
18663
|
+
3600352804,
|
|
18664
|
+
4094571909,
|
|
18665
|
+
275423344,
|
|
18666
|
+
430227734,
|
|
18667
|
+
506948616,
|
|
18668
|
+
659060556,
|
|
18669
|
+
883997877,
|
|
18670
|
+
958139571,
|
|
18671
|
+
1322822218,
|
|
18672
|
+
1537002063,
|
|
18673
|
+
1747873779,
|
|
18674
|
+
1955562222,
|
|
18675
|
+
2024104815,
|
|
18676
|
+
2227730452,
|
|
18677
|
+
2361852424,
|
|
18678
|
+
2428436474,
|
|
18679
|
+
2756734187,
|
|
18680
|
+
3204031479,
|
|
18681
|
+
3329325298
|
|
18682
|
+
]);
|
|
18683
|
+
var SHA256_W = /* @__PURE__ */ new Uint32Array(64);
|
|
18684
|
+
var SHA256 = class extends HashMD {
|
|
18685
|
+
constructor(outputLen = 32) {
|
|
18686
|
+
super(64, outputLen, 8, false);
|
|
18687
|
+
this.A = SHA256_IV[0] | 0;
|
|
18688
|
+
this.B = SHA256_IV[1] | 0;
|
|
18689
|
+
this.C = SHA256_IV[2] | 0;
|
|
18690
|
+
this.D = SHA256_IV[3] | 0;
|
|
18691
|
+
this.E = SHA256_IV[4] | 0;
|
|
18692
|
+
this.F = SHA256_IV[5] | 0;
|
|
18693
|
+
this.G = SHA256_IV[6] | 0;
|
|
18694
|
+
this.H = SHA256_IV[7] | 0;
|
|
18695
|
+
}
|
|
18696
|
+
get() {
|
|
18697
|
+
const { A, B, C, D, E, F, G, H } = this;
|
|
18698
|
+
return [A, B, C, D, E, F, G, H];
|
|
18699
|
+
}
|
|
18700
|
+
// prettier-ignore
|
|
18701
|
+
set(A, B, C, D, E, F, G, H) {
|
|
18702
|
+
this.A = A | 0;
|
|
18703
|
+
this.B = B | 0;
|
|
18704
|
+
this.C = C | 0;
|
|
18705
|
+
this.D = D | 0;
|
|
18706
|
+
this.E = E | 0;
|
|
18707
|
+
this.F = F | 0;
|
|
18708
|
+
this.G = G | 0;
|
|
18709
|
+
this.H = H | 0;
|
|
18710
|
+
}
|
|
18711
|
+
process(view2, offset) {
|
|
18712
|
+
for (let i = 0; i < 16; i++, offset += 4)
|
|
18713
|
+
SHA256_W[i] = view2.getUint32(offset, false);
|
|
18714
|
+
for (let i = 16; i < 64; i++) {
|
|
18715
|
+
const W15 = SHA256_W[i - 15];
|
|
18716
|
+
const W2 = SHA256_W[i - 2];
|
|
18717
|
+
const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ W15 >>> 3;
|
|
18718
|
+
const s1 = rotr(W2, 17) ^ rotr(W2, 19) ^ W2 >>> 10;
|
|
18719
|
+
SHA256_W[i] = s1 + SHA256_W[i - 7] + s0 + SHA256_W[i - 16] | 0;
|
|
18720
|
+
}
|
|
18721
|
+
let { A, B, C, D, E, F, G, H } = this;
|
|
18722
|
+
for (let i = 0; i < 64; i++) {
|
|
18723
|
+
const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25);
|
|
18724
|
+
const T1 = H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i] | 0;
|
|
18725
|
+
const sigma0 = rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22);
|
|
18726
|
+
const T2 = sigma0 + Maj(A, B, C) | 0;
|
|
18727
|
+
H = G;
|
|
18728
|
+
G = F;
|
|
18729
|
+
F = E;
|
|
18730
|
+
E = D + T1 | 0;
|
|
18731
|
+
D = C;
|
|
18732
|
+
C = B;
|
|
18733
|
+
B = A;
|
|
18734
|
+
A = T1 + T2 | 0;
|
|
18735
|
+
}
|
|
18736
|
+
A = A + this.A | 0;
|
|
18737
|
+
B = B + this.B | 0;
|
|
18738
|
+
C = C + this.C | 0;
|
|
18739
|
+
D = D + this.D | 0;
|
|
18740
|
+
E = E + this.E | 0;
|
|
18741
|
+
F = F + this.F | 0;
|
|
18742
|
+
G = G + this.G | 0;
|
|
18743
|
+
H = H + this.H | 0;
|
|
18744
|
+
this.set(A, B, C, D, E, F, G, H);
|
|
18745
|
+
}
|
|
18746
|
+
roundClean() {
|
|
18747
|
+
clean(SHA256_W);
|
|
18748
|
+
}
|
|
18749
|
+
destroy() {
|
|
18750
|
+
this.set(0, 0, 0, 0, 0, 0, 0, 0);
|
|
18751
|
+
clean(this.buffer);
|
|
18752
|
+
}
|
|
18753
|
+
};
|
|
18754
|
+
var K512 = /* @__PURE__ */ (() => split([
|
|
18755
|
+
"0x428a2f98d728ae22",
|
|
18756
|
+
"0x7137449123ef65cd",
|
|
18757
|
+
"0xb5c0fbcfec4d3b2f",
|
|
18758
|
+
"0xe9b5dba58189dbbc",
|
|
18759
|
+
"0x3956c25bf348b538",
|
|
18760
|
+
"0x59f111f1b605d019",
|
|
18761
|
+
"0x923f82a4af194f9b",
|
|
18762
|
+
"0xab1c5ed5da6d8118",
|
|
18763
|
+
"0xd807aa98a3030242",
|
|
18764
|
+
"0x12835b0145706fbe",
|
|
18765
|
+
"0x243185be4ee4b28c",
|
|
18766
|
+
"0x550c7dc3d5ffb4e2",
|
|
18767
|
+
"0x72be5d74f27b896f",
|
|
18768
|
+
"0x80deb1fe3b1696b1",
|
|
18769
|
+
"0x9bdc06a725c71235",
|
|
18770
|
+
"0xc19bf174cf692694",
|
|
18771
|
+
"0xe49b69c19ef14ad2",
|
|
18772
|
+
"0xefbe4786384f25e3",
|
|
18773
|
+
"0x0fc19dc68b8cd5b5",
|
|
18774
|
+
"0x240ca1cc77ac9c65",
|
|
18775
|
+
"0x2de92c6f592b0275",
|
|
18776
|
+
"0x4a7484aa6ea6e483",
|
|
18777
|
+
"0x5cb0a9dcbd41fbd4",
|
|
18778
|
+
"0x76f988da831153b5",
|
|
18779
|
+
"0x983e5152ee66dfab",
|
|
18780
|
+
"0xa831c66d2db43210",
|
|
18781
|
+
"0xb00327c898fb213f",
|
|
18782
|
+
"0xbf597fc7beef0ee4",
|
|
18783
|
+
"0xc6e00bf33da88fc2",
|
|
18784
|
+
"0xd5a79147930aa725",
|
|
18785
|
+
"0x06ca6351e003826f",
|
|
18786
|
+
"0x142929670a0e6e70",
|
|
18787
|
+
"0x27b70a8546d22ffc",
|
|
18788
|
+
"0x2e1b21385c26c926",
|
|
18789
|
+
"0x4d2c6dfc5ac42aed",
|
|
18790
|
+
"0x53380d139d95b3df",
|
|
18791
|
+
"0x650a73548baf63de",
|
|
18792
|
+
"0x766a0abb3c77b2a8",
|
|
18793
|
+
"0x81c2c92e47edaee6",
|
|
18794
|
+
"0x92722c851482353b",
|
|
18795
|
+
"0xa2bfe8a14cf10364",
|
|
18796
|
+
"0xa81a664bbc423001",
|
|
18797
|
+
"0xc24b8b70d0f89791",
|
|
18798
|
+
"0xc76c51a30654be30",
|
|
18799
|
+
"0xd192e819d6ef5218",
|
|
18800
|
+
"0xd69906245565a910",
|
|
18801
|
+
"0xf40e35855771202a",
|
|
18802
|
+
"0x106aa07032bbd1b8",
|
|
18803
|
+
"0x19a4c116b8d2d0c8",
|
|
18804
|
+
"0x1e376c085141ab53",
|
|
18805
|
+
"0x2748774cdf8eeb99",
|
|
18806
|
+
"0x34b0bcb5e19b48a8",
|
|
18807
|
+
"0x391c0cb3c5c95a63",
|
|
18808
|
+
"0x4ed8aa4ae3418acb",
|
|
18809
|
+
"0x5b9cca4f7763e373",
|
|
18810
|
+
"0x682e6ff3d6b2b8a3",
|
|
18811
|
+
"0x748f82ee5defb2fc",
|
|
18812
|
+
"0x78a5636f43172f60",
|
|
18813
|
+
"0x84c87814a1f0ab72",
|
|
18814
|
+
"0x8cc702081a6439ec",
|
|
18815
|
+
"0x90befffa23631e28",
|
|
18816
|
+
"0xa4506cebde82bde9",
|
|
18817
|
+
"0xbef9a3f7b2c67915",
|
|
18818
|
+
"0xc67178f2e372532b",
|
|
18819
|
+
"0xca273eceea26619c",
|
|
18820
|
+
"0xd186b8c721c0c207",
|
|
18821
|
+
"0xeada7dd6cde0eb1e",
|
|
18822
|
+
"0xf57d4f7fee6ed178",
|
|
18823
|
+
"0x06f067aa72176fba",
|
|
18824
|
+
"0x0a637dc5a2c898a6",
|
|
18825
|
+
"0x113f9804bef90dae",
|
|
18826
|
+
"0x1b710b35131c471b",
|
|
18827
|
+
"0x28db77f523047d84",
|
|
18828
|
+
"0x32caab7b40c72493",
|
|
18829
|
+
"0x3c9ebe0a15c9bebc",
|
|
18830
|
+
"0x431d67c49c100d4c",
|
|
18831
|
+
"0x4cc5d4becb3e42b6",
|
|
18832
|
+
"0x597f299cfc657e2a",
|
|
18833
|
+
"0x5fcb6fab3ad6faec",
|
|
18834
|
+
"0x6c44198c4a475817"
|
|
18835
|
+
].map((n) => BigInt(n))))();
|
|
18836
|
+
var SHA512_Kh = /* @__PURE__ */ (() => K512[0])();
|
|
18837
|
+
var SHA512_Kl = /* @__PURE__ */ (() => K512[1])();
|
|
18838
|
+
var SHA512_W_H = /* @__PURE__ */ new Uint32Array(80);
|
|
18839
|
+
var SHA512_W_L = /* @__PURE__ */ new Uint32Array(80);
|
|
18840
|
+
var SHA512 = class extends HashMD {
|
|
18841
|
+
constructor(outputLen = 64) {
|
|
18842
|
+
super(128, outputLen, 16, false);
|
|
18843
|
+
this.Ah = SHA512_IV[0] | 0;
|
|
18844
|
+
this.Al = SHA512_IV[1] | 0;
|
|
18845
|
+
this.Bh = SHA512_IV[2] | 0;
|
|
18846
|
+
this.Bl = SHA512_IV[3] | 0;
|
|
18847
|
+
this.Ch = SHA512_IV[4] | 0;
|
|
18848
|
+
this.Cl = SHA512_IV[5] | 0;
|
|
18849
|
+
this.Dh = SHA512_IV[6] | 0;
|
|
18850
|
+
this.Dl = SHA512_IV[7] | 0;
|
|
18851
|
+
this.Eh = SHA512_IV[8] | 0;
|
|
18852
|
+
this.El = SHA512_IV[9] | 0;
|
|
18853
|
+
this.Fh = SHA512_IV[10] | 0;
|
|
18854
|
+
this.Fl = SHA512_IV[11] | 0;
|
|
18855
|
+
this.Gh = SHA512_IV[12] | 0;
|
|
18856
|
+
this.Gl = SHA512_IV[13] | 0;
|
|
18857
|
+
this.Hh = SHA512_IV[14] | 0;
|
|
18858
|
+
this.Hl = SHA512_IV[15] | 0;
|
|
18859
|
+
}
|
|
18860
|
+
// prettier-ignore
|
|
18861
|
+
get() {
|
|
18862
|
+
const { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this;
|
|
18863
|
+
return [Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl];
|
|
18864
|
+
}
|
|
18865
|
+
// prettier-ignore
|
|
18866
|
+
set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl) {
|
|
18867
|
+
this.Ah = Ah | 0;
|
|
18868
|
+
this.Al = Al | 0;
|
|
18869
|
+
this.Bh = Bh | 0;
|
|
18870
|
+
this.Bl = Bl | 0;
|
|
18871
|
+
this.Ch = Ch | 0;
|
|
18872
|
+
this.Cl = Cl | 0;
|
|
18873
|
+
this.Dh = Dh | 0;
|
|
18874
|
+
this.Dl = Dl | 0;
|
|
18875
|
+
this.Eh = Eh | 0;
|
|
18876
|
+
this.El = El | 0;
|
|
18877
|
+
this.Fh = Fh | 0;
|
|
18878
|
+
this.Fl = Fl | 0;
|
|
18879
|
+
this.Gh = Gh | 0;
|
|
18880
|
+
this.Gl = Gl | 0;
|
|
18881
|
+
this.Hh = Hh | 0;
|
|
18882
|
+
this.Hl = Hl | 0;
|
|
18883
|
+
}
|
|
18884
|
+
process(view2, offset) {
|
|
18885
|
+
for (let i = 0; i < 16; i++, offset += 4) {
|
|
18886
|
+
SHA512_W_H[i] = view2.getUint32(offset);
|
|
18887
|
+
SHA512_W_L[i] = view2.getUint32(offset += 4);
|
|
18888
|
+
}
|
|
18889
|
+
for (let i = 16; i < 80; i++) {
|
|
18890
|
+
const W15h = SHA512_W_H[i - 15] | 0;
|
|
18891
|
+
const W15l = SHA512_W_L[i - 15] | 0;
|
|
18892
|
+
const s0h = rotrSH(W15h, W15l, 1) ^ rotrSH(W15h, W15l, 8) ^ shrSH(W15h, W15l, 7);
|
|
18893
|
+
const s0l = rotrSL(W15h, W15l, 1) ^ rotrSL(W15h, W15l, 8) ^ shrSL(W15h, W15l, 7);
|
|
18894
|
+
const W2h = SHA512_W_H[i - 2] | 0;
|
|
18895
|
+
const W2l = SHA512_W_L[i - 2] | 0;
|
|
18896
|
+
const s1h = rotrSH(W2h, W2l, 19) ^ rotrBH(W2h, W2l, 61) ^ shrSH(W2h, W2l, 6);
|
|
18897
|
+
const s1l = rotrSL(W2h, W2l, 19) ^ rotrBL(W2h, W2l, 61) ^ shrSL(W2h, W2l, 6);
|
|
18898
|
+
const SUMl = add4L(s0l, s1l, SHA512_W_L[i - 7], SHA512_W_L[i - 16]);
|
|
18899
|
+
const SUMh = add4H(SUMl, s0h, s1h, SHA512_W_H[i - 7], SHA512_W_H[i - 16]);
|
|
18900
|
+
SHA512_W_H[i] = SUMh | 0;
|
|
18901
|
+
SHA512_W_L[i] = SUMl | 0;
|
|
18902
|
+
}
|
|
18903
|
+
let { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this;
|
|
18904
|
+
for (let i = 0; i < 80; i++) {
|
|
18905
|
+
const sigma1h = rotrSH(Eh, El, 14) ^ rotrSH(Eh, El, 18) ^ rotrBH(Eh, El, 41);
|
|
18906
|
+
const sigma1l = rotrSL(Eh, El, 14) ^ rotrSL(Eh, El, 18) ^ rotrBL(Eh, El, 41);
|
|
18907
|
+
const CHIh = Eh & Fh ^ ~Eh & Gh;
|
|
18908
|
+
const CHIl = El & Fl ^ ~El & Gl;
|
|
18909
|
+
const T1ll = add5L(Hl, sigma1l, CHIl, SHA512_Kl[i], SHA512_W_L[i]);
|
|
18910
|
+
const T1h = add5H(T1ll, Hh, sigma1h, CHIh, SHA512_Kh[i], SHA512_W_H[i]);
|
|
18911
|
+
const T1l = T1ll | 0;
|
|
18912
|
+
const sigma0h = rotrSH(Ah, Al, 28) ^ rotrBH(Ah, Al, 34) ^ rotrBH(Ah, Al, 39);
|
|
18913
|
+
const sigma0l = rotrSL(Ah, Al, 28) ^ rotrBL(Ah, Al, 34) ^ rotrBL(Ah, Al, 39);
|
|
18914
|
+
const MAJh = Ah & Bh ^ Ah & Ch ^ Bh & Ch;
|
|
18915
|
+
const MAJl = Al & Bl ^ Al & Cl ^ Bl & Cl;
|
|
18916
|
+
Hh = Gh | 0;
|
|
18917
|
+
Hl = Gl | 0;
|
|
18918
|
+
Gh = Fh | 0;
|
|
18919
|
+
Gl = Fl | 0;
|
|
18920
|
+
Fh = Eh | 0;
|
|
18921
|
+
Fl = El | 0;
|
|
18922
|
+
({ h: Eh, l: El } = add(Dh | 0, Dl | 0, T1h | 0, T1l | 0));
|
|
18923
|
+
Dh = Ch | 0;
|
|
18924
|
+
Dl = Cl | 0;
|
|
18925
|
+
Ch = Bh | 0;
|
|
18926
|
+
Cl = Bl | 0;
|
|
18927
|
+
Bh = Ah | 0;
|
|
18928
|
+
Bl = Al | 0;
|
|
18929
|
+
const All = add3L(T1l, sigma0l, MAJl);
|
|
18930
|
+
Ah = add3H(All, T1h, sigma0h, MAJh);
|
|
18931
|
+
Al = All | 0;
|
|
18932
|
+
}
|
|
18933
|
+
({ h: Ah, l: Al } = add(this.Ah | 0, this.Al | 0, Ah | 0, Al | 0));
|
|
18934
|
+
({ h: Bh, l: Bl } = add(this.Bh | 0, this.Bl | 0, Bh | 0, Bl | 0));
|
|
18935
|
+
({ h: Ch, l: Cl } = add(this.Ch | 0, this.Cl | 0, Ch | 0, Cl | 0));
|
|
18936
|
+
({ h: Dh, l: Dl } = add(this.Dh | 0, this.Dl | 0, Dh | 0, Dl | 0));
|
|
18937
|
+
({ h: Eh, l: El } = add(this.Eh | 0, this.El | 0, Eh | 0, El | 0));
|
|
18938
|
+
({ h: Fh, l: Fl } = add(this.Fh | 0, this.Fl | 0, Fh | 0, Fl | 0));
|
|
18939
|
+
({ h: Gh, l: Gl } = add(this.Gh | 0, this.Gl | 0, Gh | 0, Gl | 0));
|
|
18940
|
+
({ h: Hh, l: Hl } = add(this.Hh | 0, this.Hl | 0, Hh | 0, Hl | 0));
|
|
18941
|
+
this.set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl);
|
|
18942
|
+
}
|
|
18943
|
+
roundClean() {
|
|
18944
|
+
clean(SHA512_W_H, SHA512_W_L);
|
|
18945
|
+
}
|
|
18946
|
+
destroy() {
|
|
18947
|
+
clean(this.buffer);
|
|
18948
|
+
this.set(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
|
|
18949
|
+
}
|
|
18950
|
+
};
|
|
18951
|
+
var sha256 = /* @__PURE__ */ createHasher(() => new SHA256());
|
|
18952
|
+
var sha512 = /* @__PURE__ */ createHasher(() => new SHA512());
|
|
18953
|
+
|
|
18954
|
+
// ../mmt-manifest-verify/node_modules/@noble/hashes/esm/sha256.js
|
|
18955
|
+
var sha2562 = sha256;
|
|
18956
|
+
|
|
18957
|
+
// ../mmt-manifest-verify/dist/bc-provenance-v2.js
|
|
18958
|
+
var BCPV2_HEADER_LENGTH = 68;
|
|
18959
|
+
var BCPV2_SIGNATURE_LENGTH = 64;
|
|
18960
|
+
var BCPV2_MIN_RECORD_LENGTH = BCPV2_HEADER_LENGTH + 32 + BCPV2_SIGNATURE_LENGTH;
|
|
18961
|
+
var BCPV2_MAX_RECORD_LENGTH = 1316;
|
|
18962
|
+
var BCPV2_MAX_DIGESTS = 37;
|
|
18963
|
+
var BCPV2_HASH_ALG_SHA256 = 1;
|
|
18964
|
+
var text = new TextEncoder();
|
|
18965
|
+
var MAGIC = Uint8Array.from([77, 72, 77, 50]);
|
|
18966
|
+
var SIGNING_DOMAIN = text.encode("mcast-manifest-v2\0");
|
|
18967
|
+
function equalBytes(left, right) {
|
|
18968
|
+
if (left.byteLength !== right.byteLength)
|
|
18969
|
+
return false;
|
|
18970
|
+
let different = 0;
|
|
18971
|
+
for (let index = 0; index < left.byteLength; index++)
|
|
18972
|
+
different |= left[index] ^ right[index];
|
|
18973
|
+
return different === 0;
|
|
18974
|
+
}
|
|
18975
|
+
function u16(value) {
|
|
18976
|
+
const result = new Uint8Array(2);
|
|
18977
|
+
new DataView(result.buffer).setUint16(0, value);
|
|
18978
|
+
return result;
|
|
18979
|
+
}
|
|
18980
|
+
function u322(value) {
|
|
18981
|
+
const result = new Uint8Array(4);
|
|
18982
|
+
new DataView(result.buffer).setUint32(0, value);
|
|
18983
|
+
return result;
|
|
18984
|
+
}
|
|
18985
|
+
function validU32(value) {
|
|
18986
|
+
return Number.isInteger(value) && value >= 0 && value <= 4294967295;
|
|
18987
|
+
}
|
|
18988
|
+
function validPort(value) {
|
|
18989
|
+
return Number.isInteger(value) && value >= 0 && value <= 65535;
|
|
18990
|
+
}
|
|
18991
|
+
function parseIpv4(address) {
|
|
18992
|
+
const fields = address.split(".");
|
|
18993
|
+
if (fields.length !== 4 || fields.some((field) => !/^\d+$/.test(field))) {
|
|
18994
|
+
throw new Error(`invalid IPv4 address ${address}`);
|
|
18995
|
+
}
|
|
18996
|
+
const values = fields.map(Number);
|
|
18997
|
+
if (values.some((value, index) => value > 255 || String(value) !== fields[index])) {
|
|
18998
|
+
throw new Error(`invalid IPv4 address ${address}`);
|
|
18999
|
+
}
|
|
19000
|
+
return Uint8Array.from(values);
|
|
19001
|
+
}
|
|
19002
|
+
function parseIpv6(address) {
|
|
19003
|
+
const compressed = address.includes("::");
|
|
19004
|
+
if ((address.match(/::/g) ?? []).length > 1)
|
|
19005
|
+
throw new Error(`invalid IPv6 address ${address}`);
|
|
19006
|
+
const [leftText, rightText = ""] = address.split("::");
|
|
19007
|
+
const left = leftText ? leftText.split(":") : [];
|
|
19008
|
+
const right = rightText ? rightText.split(":") : [];
|
|
19009
|
+
if (left.some((group) => group.includes(".")) || right.some((group) => group.includes("."))) {
|
|
19010
|
+
throw new Error(`invalid IPv6 address ${address}`);
|
|
19011
|
+
}
|
|
19012
|
+
const missing = 8 - left.length - right.length;
|
|
19013
|
+
if (missing < 0 || !compressed && missing !== 0 || compressed && missing < 1) {
|
|
19014
|
+
throw new Error(`invalid IPv6 address ${address}`);
|
|
19015
|
+
}
|
|
19016
|
+
const groups = [...left, ...Array.from({ length: missing }, () => "0"), ...right];
|
|
19017
|
+
if (groups.length !== 8 || groups.some((group) => !/^[0-9a-f]{1,4}$/i.test(group))) {
|
|
19018
|
+
throw new Error(`invalid IPv6 address ${address}`);
|
|
19019
|
+
}
|
|
19020
|
+
const result = new Uint8Array(16);
|
|
19021
|
+
const view2 = new DataView(result.buffer);
|
|
19022
|
+
groups.forEach((group, index) => view2.setUint16(index * 2, Number.parseInt(group, 16)));
|
|
19023
|
+
return result;
|
|
19024
|
+
}
|
|
19025
|
+
function parseBcProvenanceV2Address(address) {
|
|
19026
|
+
if (address.includes(":"))
|
|
19027
|
+
return parseIpv6(address);
|
|
19028
|
+
return parseIpv4(address);
|
|
19029
|
+
}
|
|
19030
|
+
function endpointBytes(endpoint) {
|
|
19031
|
+
if (!validPort(endpoint.port) || !validPort(endpoint.manifestPort) || !validU32(endpoint.streamId)) {
|
|
19032
|
+
throw new Error("invalid BCPV2 endpoint");
|
|
19033
|
+
}
|
|
19034
|
+
const source = parseBcProvenanceV2Address(endpoint.sourceAddress);
|
|
19035
|
+
const group = parseBcProvenanceV2Address(endpoint.groupAddress);
|
|
19036
|
+
if (source.byteLength !== group.byteLength)
|
|
19037
|
+
throw new Error("endpoint address families differ");
|
|
19038
|
+
return { source, group };
|
|
19039
|
+
}
|
|
19040
|
+
function readU32(view2, offset) {
|
|
19041
|
+
return view2.getUint32(offset);
|
|
19042
|
+
}
|
|
19043
|
+
function readU642(view2, offset) {
|
|
19044
|
+
return view2.getBigUint64(offset);
|
|
19045
|
+
}
|
|
19046
|
+
function decodeBcProvenanceV2Record(input) {
|
|
19047
|
+
const bytes = Uint8Array.from(input);
|
|
19048
|
+
if (bytes.byteLength < BCPV2_MIN_RECORD_LENGTH || bytes.byteLength > BCPV2_MAX_RECORD_LENGTH || (bytes.byteLength - 132) % 32 !== 0) {
|
|
19049
|
+
throw new Error("invalid BCPV2 record length");
|
|
19050
|
+
}
|
|
19051
|
+
const view2 = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
19052
|
+
if (!equalBytes(bytes.subarray(0, 4), MAGIC))
|
|
19053
|
+
throw new Error("invalid BCPV2 record magic");
|
|
19054
|
+
const count = view2.getUint16(28);
|
|
19055
|
+
if (count < 1 || count > BCPV2_MAX_DIGESTS || bytes.byteLength !== 132 + count * 32) {
|
|
19056
|
+
throw new Error("invalid BCPV2 record count");
|
|
19057
|
+
}
|
|
19058
|
+
const body = bytes.slice(0, bytes.byteLength - BCPV2_SIGNATURE_LENGTH);
|
|
19059
|
+
const digests = [];
|
|
19060
|
+
for (let index = 0; index < count; index++) {
|
|
19061
|
+
digests.push(bytes.slice(BCPV2_HEADER_LENGTH + index * 32, BCPV2_HEADER_LENGTH + (index + 1) * 32));
|
|
19062
|
+
}
|
|
19063
|
+
return {
|
|
19064
|
+
kid: readU32(view2, 4),
|
|
19065
|
+
epoch: readU32(view2, 8),
|
|
19066
|
+
mseq: readU642(view2, 12),
|
|
19067
|
+
firstSeq: readU642(view2, 20),
|
|
19068
|
+
count,
|
|
19069
|
+
hashAlg: bytes[30],
|
|
19070
|
+
flags: bytes[31],
|
|
19071
|
+
sent: readU32(view2, 32),
|
|
19072
|
+
ctx: bytes.slice(36, 68),
|
|
19073
|
+
digests,
|
|
19074
|
+
signature: bytes.slice(bytes.byteLength - BCPV2_SIGNATURE_LENGTH),
|
|
19075
|
+
body,
|
|
19076
|
+
raw: bytes
|
|
19077
|
+
};
|
|
19078
|
+
}
|
|
19079
|
+
function bcProvenanceV2SignedMessage(record, endpoint) {
|
|
19080
|
+
const { source, group } = endpointBytes(endpoint);
|
|
19081
|
+
const prefix = new Uint8Array(SIGNING_DOMAIN.byteLength + 4 + source.byteLength + group.byteLength + 2);
|
|
19082
|
+
let offset = 0;
|
|
19083
|
+
prefix.set(SIGNING_DOMAIN, offset);
|
|
19084
|
+
offset += SIGNING_DOMAIN.byteLength;
|
|
19085
|
+
prefix.set(u322(endpoint.streamId), offset);
|
|
19086
|
+
offset += 4;
|
|
19087
|
+
prefix.set(source, offset);
|
|
19088
|
+
offset += source.byteLength;
|
|
19089
|
+
prefix.set(group, offset);
|
|
19090
|
+
offset += group.byteLength;
|
|
19091
|
+
prefix.set(u16(endpoint.manifestPort), offset);
|
|
19092
|
+
const message = new Uint8Array(prefix.byteLength + record.body.byteLength);
|
|
19093
|
+
message.set(prefix);
|
|
19094
|
+
message.set(record.body, prefix.byteLength);
|
|
19095
|
+
return message;
|
|
19096
|
+
}
|
|
19097
|
+
function bcProvenanceV2DatagramDigest(endpoint, payload) {
|
|
19098
|
+
if (payload.byteLength > 65535)
|
|
19099
|
+
throw new Error("datagram payload exceeds u16 length");
|
|
19100
|
+
const { source, group } = endpointBytes(endpoint);
|
|
19101
|
+
const pseudoheader = new Uint8Array(source.byteLength + group.byteLength + 2 + 2 + 2 + 2 + 4);
|
|
19102
|
+
let offset = 0;
|
|
19103
|
+
pseudoheader.set(source, offset);
|
|
19104
|
+
offset += source.byteLength;
|
|
19105
|
+
pseudoheader.set(group, offset);
|
|
19106
|
+
offset += group.byteLength;
|
|
19107
|
+
pseudoheader.set([0, 17], offset);
|
|
19108
|
+
offset += 2;
|
|
19109
|
+
pseudoheader.set(u16(payload.byteLength), offset);
|
|
19110
|
+
offset += 2;
|
|
19111
|
+
pseudoheader.set(u16(endpoint.port), offset);
|
|
19112
|
+
offset += 2;
|
|
19113
|
+
pseudoheader.set(u16(endpoint.port), offset);
|
|
19114
|
+
offset += 2;
|
|
19115
|
+
pseudoheader.set(u322(endpoint.streamId), offset);
|
|
19116
|
+
const input = new Uint8Array(pseudoheader.byteLength + payload.byteLength);
|
|
19117
|
+
input.set(pseudoheader);
|
|
19118
|
+
input.set(payload, pseudoheader.byteLength);
|
|
19119
|
+
return sha2562(input);
|
|
19120
|
+
}
|
|
19121
|
+
|
|
19122
|
+
// ../mmt-manifest-verify/node_modules/@noble/curves/esm/utils.js
|
|
19123
|
+
var _0n = /* @__PURE__ */ BigInt(0);
|
|
19124
|
+
var _1n = /* @__PURE__ */ BigInt(1);
|
|
19125
|
+
function _abool2(value, title = "") {
|
|
19126
|
+
if (typeof value !== "boolean") {
|
|
19127
|
+
const prefix = title && `"${title}"`;
|
|
19128
|
+
throw new Error(prefix + "expected boolean, got type=" + typeof value);
|
|
19129
|
+
}
|
|
19130
|
+
return value;
|
|
19131
|
+
}
|
|
19132
|
+
function _abytes2(value, length, title = "") {
|
|
19133
|
+
const bytes = isBytes(value);
|
|
19134
|
+
const len = value?.length;
|
|
19135
|
+
const needsLen = length !== void 0;
|
|
19136
|
+
if (!bytes || needsLen && len !== length) {
|
|
19137
|
+
const prefix = title && `"${title}" `;
|
|
19138
|
+
const ofLen = needsLen ? ` of length ${length}` : "";
|
|
19139
|
+
const got = bytes ? `length=${len}` : `type=${typeof value}`;
|
|
19140
|
+
throw new Error(prefix + "expected Uint8Array" + ofLen + ", got " + got);
|
|
19141
|
+
}
|
|
19142
|
+
return value;
|
|
19143
|
+
}
|
|
19144
|
+
function hexToNumber(hex2) {
|
|
19145
|
+
if (typeof hex2 !== "string")
|
|
19146
|
+
throw new Error("hex string expected, got " + typeof hex2);
|
|
19147
|
+
return hex2 === "" ? _0n : BigInt("0x" + hex2);
|
|
19148
|
+
}
|
|
19149
|
+
function bytesToNumberBE(bytes) {
|
|
19150
|
+
return hexToNumber(bytesToHex(bytes));
|
|
19151
|
+
}
|
|
19152
|
+
function bytesToNumberLE(bytes) {
|
|
19153
|
+
abytes(bytes);
|
|
19154
|
+
return hexToNumber(bytesToHex(Uint8Array.from(bytes).reverse()));
|
|
19155
|
+
}
|
|
19156
|
+
function numberToBytesBE(n, len) {
|
|
19157
|
+
return hexToBytes(n.toString(16).padStart(len * 2, "0"));
|
|
19158
|
+
}
|
|
19159
|
+
function numberToBytesLE(n, len) {
|
|
19160
|
+
return numberToBytesBE(n, len).reverse();
|
|
19161
|
+
}
|
|
19162
|
+
function ensureBytes(title, hex2, expectedLength) {
|
|
19163
|
+
let res;
|
|
19164
|
+
if (typeof hex2 === "string") {
|
|
19165
|
+
try {
|
|
19166
|
+
res = hexToBytes(hex2);
|
|
19167
|
+
} catch (e) {
|
|
19168
|
+
throw new Error(title + " must be hex string or Uint8Array, cause: " + e);
|
|
19169
|
+
}
|
|
19170
|
+
} else if (isBytes(hex2)) {
|
|
19171
|
+
res = Uint8Array.from(hex2);
|
|
19172
|
+
} else {
|
|
19173
|
+
throw new Error(title + " must be hex string or Uint8Array");
|
|
19174
|
+
}
|
|
19175
|
+
const len = res.length;
|
|
19176
|
+
if (typeof expectedLength === "number" && len !== expectedLength)
|
|
19177
|
+
throw new Error(title + " of length " + expectedLength + " expected, got " + len);
|
|
19178
|
+
return res;
|
|
19179
|
+
}
|
|
19180
|
+
function equalBytes2(a, b) {
|
|
19181
|
+
if (a.length !== b.length)
|
|
19182
|
+
return false;
|
|
19183
|
+
let diff = 0;
|
|
19184
|
+
for (let i = 0; i < a.length; i++)
|
|
19185
|
+
diff |= a[i] ^ b[i];
|
|
19186
|
+
return diff === 0;
|
|
19187
|
+
}
|
|
19188
|
+
function copyBytes(bytes) {
|
|
19189
|
+
return Uint8Array.from(bytes);
|
|
19190
|
+
}
|
|
19191
|
+
var isPosBig = (n) => typeof n === "bigint" && _0n <= n;
|
|
19192
|
+
function inRange(n, min, max) {
|
|
19193
|
+
return isPosBig(n) && isPosBig(min) && isPosBig(max) && min <= n && n < max;
|
|
19194
|
+
}
|
|
19195
|
+
function aInRange(title, n, min, max) {
|
|
19196
|
+
if (!inRange(n, min, max))
|
|
19197
|
+
throw new Error("expected valid " + title + ": " + min + " <= n < " + max + ", got " + n);
|
|
19198
|
+
}
|
|
19199
|
+
function bitLen(n) {
|
|
19200
|
+
let len;
|
|
19201
|
+
for (len = 0; n > _0n; n >>= _1n, len += 1)
|
|
19202
|
+
;
|
|
19203
|
+
return len;
|
|
19204
|
+
}
|
|
19205
|
+
var bitMask = (n) => (_1n << BigInt(n)) - _1n;
|
|
19206
|
+
function _validateObject(object, fields, optFields = {}) {
|
|
19207
|
+
if (!object || typeof object !== "object")
|
|
19208
|
+
throw new Error("expected valid options object");
|
|
19209
|
+
function checkField(fieldName, expectedType, isOpt) {
|
|
19210
|
+
const val = object[fieldName];
|
|
19211
|
+
if (isOpt && val === void 0)
|
|
19212
|
+
return;
|
|
19213
|
+
const current = typeof val;
|
|
19214
|
+
if (current !== expectedType || val === null)
|
|
19215
|
+
throw new Error(`param "${fieldName}" is invalid: expected ${expectedType}, got ${current}`);
|
|
19216
|
+
}
|
|
19217
|
+
Object.entries(fields).forEach(([k, v]) => checkField(k, v, false));
|
|
19218
|
+
Object.entries(optFields).forEach(([k, v]) => checkField(k, v, true));
|
|
19219
|
+
}
|
|
19220
|
+
var notImplemented = () => {
|
|
19221
|
+
throw new Error("not implemented");
|
|
19222
|
+
};
|
|
19223
|
+
function memoized(fn) {
|
|
19224
|
+
const map = /* @__PURE__ */ new WeakMap();
|
|
19225
|
+
return (arg, ...args) => {
|
|
19226
|
+
const val = map.get(arg);
|
|
19227
|
+
if (val !== void 0)
|
|
19228
|
+
return val;
|
|
19229
|
+
const computed = fn(arg, ...args);
|
|
19230
|
+
map.set(arg, computed);
|
|
19231
|
+
return computed;
|
|
19232
|
+
};
|
|
19233
|
+
}
|
|
19234
|
+
|
|
19235
|
+
// ../mmt-manifest-verify/node_modules/@noble/curves/esm/abstract/modular.js
|
|
19236
|
+
var _0n2 = BigInt(0);
|
|
19237
|
+
var _1n2 = BigInt(1);
|
|
19238
|
+
var _2n = /* @__PURE__ */ BigInt(2);
|
|
19239
|
+
var _3n = /* @__PURE__ */ BigInt(3);
|
|
19240
|
+
var _4n = /* @__PURE__ */ BigInt(4);
|
|
19241
|
+
var _5n = /* @__PURE__ */ BigInt(5);
|
|
19242
|
+
var _7n = /* @__PURE__ */ BigInt(7);
|
|
19243
|
+
var _8n = /* @__PURE__ */ BigInt(8);
|
|
19244
|
+
var _9n = /* @__PURE__ */ BigInt(9);
|
|
19245
|
+
var _16n = /* @__PURE__ */ BigInt(16);
|
|
19246
|
+
function mod(a, b) {
|
|
19247
|
+
const result = a % b;
|
|
19248
|
+
return result >= _0n2 ? result : b + result;
|
|
19249
|
+
}
|
|
19250
|
+
function pow2(x, power, modulo) {
|
|
19251
|
+
let res = x;
|
|
19252
|
+
while (power-- > _0n2) {
|
|
19253
|
+
res *= res;
|
|
19254
|
+
res %= modulo;
|
|
19255
|
+
}
|
|
19256
|
+
return res;
|
|
19257
|
+
}
|
|
19258
|
+
function invert(number, modulo) {
|
|
19259
|
+
if (number === _0n2)
|
|
19260
|
+
throw new Error("invert: expected non-zero number");
|
|
19261
|
+
if (modulo <= _0n2)
|
|
19262
|
+
throw new Error("invert: expected positive modulus, got " + modulo);
|
|
19263
|
+
let a = mod(number, modulo);
|
|
19264
|
+
let b = modulo;
|
|
19265
|
+
let x = _0n2, y = _1n2, u = _1n2, v = _0n2;
|
|
19266
|
+
while (a !== _0n2) {
|
|
19267
|
+
const q = b / a;
|
|
19268
|
+
const r = b % a;
|
|
19269
|
+
const m = x - u * q;
|
|
19270
|
+
const n = y - v * q;
|
|
19271
|
+
b = a, a = r, x = u, y = v, u = m, v = n;
|
|
19272
|
+
}
|
|
19273
|
+
const gcd = b;
|
|
19274
|
+
if (gcd !== _1n2)
|
|
19275
|
+
throw new Error("invert: does not exist");
|
|
19276
|
+
return mod(x, modulo);
|
|
19277
|
+
}
|
|
19278
|
+
function assertIsSquare(Fp2, root, n) {
|
|
19279
|
+
if (!Fp2.eql(Fp2.sqr(root), n))
|
|
19280
|
+
throw new Error("Cannot find square root");
|
|
19281
|
+
}
|
|
19282
|
+
function sqrt3mod4(Fp2, n) {
|
|
19283
|
+
const p1div4 = (Fp2.ORDER + _1n2) / _4n;
|
|
19284
|
+
const root = Fp2.pow(n, p1div4);
|
|
19285
|
+
assertIsSquare(Fp2, root, n);
|
|
19286
|
+
return root;
|
|
19287
|
+
}
|
|
19288
|
+
function sqrt5mod8(Fp2, n) {
|
|
19289
|
+
const p5div8 = (Fp2.ORDER - _5n) / _8n;
|
|
19290
|
+
const n2 = Fp2.mul(n, _2n);
|
|
19291
|
+
const v = Fp2.pow(n2, p5div8);
|
|
19292
|
+
const nv = Fp2.mul(n, v);
|
|
19293
|
+
const i = Fp2.mul(Fp2.mul(nv, _2n), v);
|
|
19294
|
+
const root = Fp2.mul(nv, Fp2.sub(i, Fp2.ONE));
|
|
19295
|
+
assertIsSquare(Fp2, root, n);
|
|
19296
|
+
return root;
|
|
19297
|
+
}
|
|
19298
|
+
function sqrt9mod16(P) {
|
|
19299
|
+
const Fp_ = Field(P);
|
|
19300
|
+
const tn = tonelliShanks(P);
|
|
19301
|
+
const c1 = tn(Fp_, Fp_.neg(Fp_.ONE));
|
|
19302
|
+
const c2 = tn(Fp_, c1);
|
|
19303
|
+
const c3 = tn(Fp_, Fp_.neg(c1));
|
|
19304
|
+
const c4 = (P + _7n) / _16n;
|
|
19305
|
+
return (Fp2, n) => {
|
|
19306
|
+
let tv1 = Fp2.pow(n, c4);
|
|
19307
|
+
let tv2 = Fp2.mul(tv1, c1);
|
|
19308
|
+
const tv3 = Fp2.mul(tv1, c2);
|
|
19309
|
+
const tv4 = Fp2.mul(tv1, c3);
|
|
19310
|
+
const e1 = Fp2.eql(Fp2.sqr(tv2), n);
|
|
19311
|
+
const e2 = Fp2.eql(Fp2.sqr(tv3), n);
|
|
19312
|
+
tv1 = Fp2.cmov(tv1, tv2, e1);
|
|
19313
|
+
tv2 = Fp2.cmov(tv4, tv3, e2);
|
|
19314
|
+
const e3 = Fp2.eql(Fp2.sqr(tv2), n);
|
|
19315
|
+
const root = Fp2.cmov(tv1, tv2, e3);
|
|
19316
|
+
assertIsSquare(Fp2, root, n);
|
|
19317
|
+
return root;
|
|
19318
|
+
};
|
|
19319
|
+
}
|
|
19320
|
+
function tonelliShanks(P) {
|
|
19321
|
+
if (P < _3n)
|
|
19322
|
+
throw new Error("sqrt is not defined for small field");
|
|
19323
|
+
let Q = P - _1n2;
|
|
19324
|
+
let S = 0;
|
|
19325
|
+
while (Q % _2n === _0n2) {
|
|
19326
|
+
Q /= _2n;
|
|
19327
|
+
S++;
|
|
19328
|
+
}
|
|
19329
|
+
let Z = _2n;
|
|
19330
|
+
const _Fp = Field(P);
|
|
19331
|
+
while (FpLegendre(_Fp, Z) === 1) {
|
|
19332
|
+
if (Z++ > 1e3)
|
|
19333
|
+
throw new Error("Cannot find square root: probably non-prime P");
|
|
19334
|
+
}
|
|
19335
|
+
if (S === 1)
|
|
19336
|
+
return sqrt3mod4;
|
|
19337
|
+
let cc = _Fp.pow(Z, Q);
|
|
19338
|
+
const Q1div2 = (Q + _1n2) / _2n;
|
|
19339
|
+
return function tonelliSlow(Fp2, n) {
|
|
19340
|
+
if (Fp2.is0(n))
|
|
19341
|
+
return n;
|
|
19342
|
+
if (FpLegendre(Fp2, n) !== 1)
|
|
19343
|
+
throw new Error("Cannot find square root");
|
|
19344
|
+
let M = S;
|
|
19345
|
+
let c = Fp2.mul(Fp2.ONE, cc);
|
|
19346
|
+
let t = Fp2.pow(n, Q);
|
|
19347
|
+
let R = Fp2.pow(n, Q1div2);
|
|
19348
|
+
while (!Fp2.eql(t, Fp2.ONE)) {
|
|
19349
|
+
if (Fp2.is0(t))
|
|
19350
|
+
return Fp2.ZERO;
|
|
19351
|
+
let i = 1;
|
|
19352
|
+
let t_tmp = Fp2.sqr(t);
|
|
19353
|
+
while (!Fp2.eql(t_tmp, Fp2.ONE)) {
|
|
19354
|
+
i++;
|
|
19355
|
+
t_tmp = Fp2.sqr(t_tmp);
|
|
19356
|
+
if (i === M)
|
|
19357
|
+
throw new Error("Cannot find square root");
|
|
19358
|
+
}
|
|
19359
|
+
const exponent = _1n2 << BigInt(M - i - 1);
|
|
19360
|
+
const b = Fp2.pow(c, exponent);
|
|
19361
|
+
M = i;
|
|
19362
|
+
c = Fp2.sqr(b);
|
|
19363
|
+
t = Fp2.mul(t, c);
|
|
19364
|
+
R = Fp2.mul(R, b);
|
|
19365
|
+
}
|
|
19366
|
+
return R;
|
|
19367
|
+
};
|
|
19368
|
+
}
|
|
19369
|
+
function FpSqrt(P) {
|
|
19370
|
+
if (P % _4n === _3n)
|
|
19371
|
+
return sqrt3mod4;
|
|
19372
|
+
if (P % _8n === _5n)
|
|
19373
|
+
return sqrt5mod8;
|
|
19374
|
+
if (P % _16n === _9n)
|
|
19375
|
+
return sqrt9mod16(P);
|
|
19376
|
+
return tonelliShanks(P);
|
|
19377
|
+
}
|
|
19378
|
+
var isNegativeLE = (num, modulo) => (mod(num, modulo) & _1n2) === _1n2;
|
|
19379
|
+
var FIELD_FIELDS = [
|
|
19380
|
+
"create",
|
|
19381
|
+
"isValid",
|
|
19382
|
+
"is0",
|
|
19383
|
+
"neg",
|
|
19384
|
+
"inv",
|
|
19385
|
+
"sqrt",
|
|
19386
|
+
"sqr",
|
|
19387
|
+
"eql",
|
|
19388
|
+
"add",
|
|
19389
|
+
"sub",
|
|
19390
|
+
"mul",
|
|
19391
|
+
"pow",
|
|
19392
|
+
"div",
|
|
19393
|
+
"addN",
|
|
19394
|
+
"subN",
|
|
19395
|
+
"mulN",
|
|
19396
|
+
"sqrN"
|
|
19397
|
+
];
|
|
19398
|
+
function validateField(field) {
|
|
19399
|
+
const initial = {
|
|
19400
|
+
ORDER: "bigint",
|
|
19401
|
+
MASK: "bigint",
|
|
19402
|
+
BYTES: "number",
|
|
19403
|
+
BITS: "number"
|
|
19404
|
+
};
|
|
19405
|
+
const opts = FIELD_FIELDS.reduce((map, val) => {
|
|
19406
|
+
map[val] = "function";
|
|
19407
|
+
return map;
|
|
19408
|
+
}, initial);
|
|
19409
|
+
_validateObject(field, opts);
|
|
19410
|
+
return field;
|
|
19411
|
+
}
|
|
19412
|
+
function FpPow(Fp2, num, power) {
|
|
19413
|
+
if (power < _0n2)
|
|
19414
|
+
throw new Error("invalid exponent, negatives unsupported");
|
|
19415
|
+
if (power === _0n2)
|
|
19416
|
+
return Fp2.ONE;
|
|
19417
|
+
if (power === _1n2)
|
|
19418
|
+
return num;
|
|
19419
|
+
let p = Fp2.ONE;
|
|
19420
|
+
let d = num;
|
|
19421
|
+
while (power > _0n2) {
|
|
19422
|
+
if (power & _1n2)
|
|
19423
|
+
p = Fp2.mul(p, d);
|
|
19424
|
+
d = Fp2.sqr(d);
|
|
19425
|
+
power >>= _1n2;
|
|
19426
|
+
}
|
|
19427
|
+
return p;
|
|
19428
|
+
}
|
|
19429
|
+
function FpInvertBatch(Fp2, nums, passZero = false) {
|
|
19430
|
+
const inverted = new Array(nums.length).fill(passZero ? Fp2.ZERO : void 0);
|
|
19431
|
+
const multipliedAcc = nums.reduce((acc, num, i) => {
|
|
19432
|
+
if (Fp2.is0(num))
|
|
19433
|
+
return acc;
|
|
19434
|
+
inverted[i] = acc;
|
|
19435
|
+
return Fp2.mul(acc, num);
|
|
19436
|
+
}, Fp2.ONE);
|
|
19437
|
+
const invertedAcc = Fp2.inv(multipliedAcc);
|
|
19438
|
+
nums.reduceRight((acc, num, i) => {
|
|
19439
|
+
if (Fp2.is0(num))
|
|
19440
|
+
return acc;
|
|
19441
|
+
inverted[i] = Fp2.mul(acc, inverted[i]);
|
|
19442
|
+
return Fp2.mul(acc, num);
|
|
19443
|
+
}, invertedAcc);
|
|
19444
|
+
return inverted;
|
|
19445
|
+
}
|
|
19446
|
+
function FpLegendre(Fp2, n) {
|
|
19447
|
+
const p1mod2 = (Fp2.ORDER - _1n2) / _2n;
|
|
19448
|
+
const powered = Fp2.pow(n, p1mod2);
|
|
19449
|
+
const yes = Fp2.eql(powered, Fp2.ONE);
|
|
19450
|
+
const zero = Fp2.eql(powered, Fp2.ZERO);
|
|
19451
|
+
const no = Fp2.eql(powered, Fp2.neg(Fp2.ONE));
|
|
19452
|
+
if (!yes && !zero && !no)
|
|
19453
|
+
throw new Error("invalid Legendre symbol result");
|
|
19454
|
+
return yes ? 1 : zero ? 0 : -1;
|
|
19455
|
+
}
|
|
19456
|
+
function nLength(n, nBitLength) {
|
|
19457
|
+
if (nBitLength !== void 0)
|
|
19458
|
+
anumber(nBitLength);
|
|
19459
|
+
const _nBitLength = nBitLength !== void 0 ? nBitLength : n.toString(2).length;
|
|
19460
|
+
const nByteLength = Math.ceil(_nBitLength / 8);
|
|
19461
|
+
return { nBitLength: _nBitLength, nByteLength };
|
|
19462
|
+
}
|
|
19463
|
+
function Field(ORDER, bitLenOrOpts, isLE = false, opts = {}) {
|
|
19464
|
+
if (ORDER <= _0n2)
|
|
19465
|
+
throw new Error("invalid field: expected ORDER > 0, got " + ORDER);
|
|
19466
|
+
let _nbitLength = void 0;
|
|
19467
|
+
let _sqrt = void 0;
|
|
19468
|
+
let modFromBytes = false;
|
|
19469
|
+
let allowedLengths = void 0;
|
|
19470
|
+
if (typeof bitLenOrOpts === "object" && bitLenOrOpts != null) {
|
|
19471
|
+
if (opts.sqrt || isLE)
|
|
19472
|
+
throw new Error("cannot specify opts in two arguments");
|
|
19473
|
+
const _opts = bitLenOrOpts;
|
|
19474
|
+
if (_opts.BITS)
|
|
19475
|
+
_nbitLength = _opts.BITS;
|
|
19476
|
+
if (_opts.sqrt)
|
|
19477
|
+
_sqrt = _opts.sqrt;
|
|
19478
|
+
if (typeof _opts.isLE === "boolean")
|
|
19479
|
+
isLE = _opts.isLE;
|
|
19480
|
+
if (typeof _opts.modFromBytes === "boolean")
|
|
19481
|
+
modFromBytes = _opts.modFromBytes;
|
|
19482
|
+
allowedLengths = _opts.allowedLengths;
|
|
19483
|
+
} else {
|
|
19484
|
+
if (typeof bitLenOrOpts === "number")
|
|
19485
|
+
_nbitLength = bitLenOrOpts;
|
|
19486
|
+
if (opts.sqrt)
|
|
19487
|
+
_sqrt = opts.sqrt;
|
|
19488
|
+
}
|
|
19489
|
+
const { nBitLength: BITS, nByteLength: BYTES } = nLength(ORDER, _nbitLength);
|
|
19490
|
+
if (BYTES > 2048)
|
|
19491
|
+
throw new Error("invalid field: expected ORDER of <= 2048 bytes");
|
|
19492
|
+
let sqrtP;
|
|
19493
|
+
const f = Object.freeze({
|
|
19494
|
+
ORDER,
|
|
19495
|
+
isLE,
|
|
19496
|
+
BITS,
|
|
19497
|
+
BYTES,
|
|
19498
|
+
MASK: bitMask(BITS),
|
|
19499
|
+
ZERO: _0n2,
|
|
19500
|
+
ONE: _1n2,
|
|
19501
|
+
allowedLengths,
|
|
19502
|
+
create: (num) => mod(num, ORDER),
|
|
19503
|
+
isValid: (num) => {
|
|
19504
|
+
if (typeof num !== "bigint")
|
|
19505
|
+
throw new Error("invalid field element: expected bigint, got " + typeof num);
|
|
19506
|
+
return _0n2 <= num && num < ORDER;
|
|
19507
|
+
},
|
|
19508
|
+
is0: (num) => num === _0n2,
|
|
19509
|
+
// is valid and invertible
|
|
19510
|
+
isValidNot0: (num) => !f.is0(num) && f.isValid(num),
|
|
19511
|
+
isOdd: (num) => (num & _1n2) === _1n2,
|
|
19512
|
+
neg: (num) => mod(-num, ORDER),
|
|
19513
|
+
eql: (lhs, rhs) => lhs === rhs,
|
|
19514
|
+
sqr: (num) => mod(num * num, ORDER),
|
|
19515
|
+
add: (lhs, rhs) => mod(lhs + rhs, ORDER),
|
|
19516
|
+
sub: (lhs, rhs) => mod(lhs - rhs, ORDER),
|
|
19517
|
+
mul: (lhs, rhs) => mod(lhs * rhs, ORDER),
|
|
19518
|
+
pow: (num, power) => FpPow(f, num, power),
|
|
19519
|
+
div: (lhs, rhs) => mod(lhs * invert(rhs, ORDER), ORDER),
|
|
19520
|
+
// Same as above, but doesn't normalize
|
|
19521
|
+
sqrN: (num) => num * num,
|
|
19522
|
+
addN: (lhs, rhs) => lhs + rhs,
|
|
19523
|
+
subN: (lhs, rhs) => lhs - rhs,
|
|
19524
|
+
mulN: (lhs, rhs) => lhs * rhs,
|
|
19525
|
+
inv: (num) => invert(num, ORDER),
|
|
19526
|
+
sqrt: _sqrt || ((n) => {
|
|
19527
|
+
if (!sqrtP)
|
|
19528
|
+
sqrtP = FpSqrt(ORDER);
|
|
19529
|
+
return sqrtP(f, n);
|
|
19530
|
+
}),
|
|
19531
|
+
toBytes: (num) => isLE ? numberToBytesLE(num, BYTES) : numberToBytesBE(num, BYTES),
|
|
19532
|
+
fromBytes: (bytes, skipValidation = true) => {
|
|
19533
|
+
if (allowedLengths) {
|
|
19534
|
+
if (!allowedLengths.includes(bytes.length) || bytes.length > BYTES) {
|
|
19535
|
+
throw new Error("Field.fromBytes: expected " + allowedLengths + " bytes, got " + bytes.length);
|
|
19536
|
+
}
|
|
19537
|
+
const padded = new Uint8Array(BYTES);
|
|
19538
|
+
padded.set(bytes, isLE ? 0 : padded.length - bytes.length);
|
|
19539
|
+
bytes = padded;
|
|
19540
|
+
}
|
|
19541
|
+
if (bytes.length !== BYTES)
|
|
19542
|
+
throw new Error("Field.fromBytes: expected " + BYTES + " bytes, got " + bytes.length);
|
|
19543
|
+
let scalar = isLE ? bytesToNumberLE(bytes) : bytesToNumberBE(bytes);
|
|
19544
|
+
if (modFromBytes)
|
|
19545
|
+
scalar = mod(scalar, ORDER);
|
|
19546
|
+
if (!skipValidation) {
|
|
19547
|
+
if (!f.isValid(scalar))
|
|
19548
|
+
throw new Error("invalid field element: outside of range 0..ORDER");
|
|
19549
|
+
}
|
|
19550
|
+
return scalar;
|
|
19551
|
+
},
|
|
19552
|
+
// TODO: we don't need it here, move out to separate fn
|
|
19553
|
+
invertBatch: (lst) => FpInvertBatch(f, lst),
|
|
19554
|
+
// We can't move this out because Fp6, Fp12 implement it
|
|
19555
|
+
// and it's unclear what to return in there.
|
|
19556
|
+
cmov: (a, b, c) => c ? b : a
|
|
19557
|
+
});
|
|
19558
|
+
return Object.freeze(f);
|
|
19559
|
+
}
|
|
19560
|
+
|
|
19561
|
+
// ../mmt-manifest-verify/node_modules/@noble/curves/esm/abstract/curve.js
|
|
19562
|
+
var _0n3 = BigInt(0);
|
|
19563
|
+
var _1n3 = BigInt(1);
|
|
19564
|
+
function negateCt(condition, item) {
|
|
19565
|
+
const neg = item.negate();
|
|
19566
|
+
return condition ? neg : item;
|
|
19567
|
+
}
|
|
19568
|
+
function normalizeZ(c, points) {
|
|
19569
|
+
const invertedZs = FpInvertBatch(c.Fp, points.map((p) => p.Z));
|
|
19570
|
+
return points.map((p, i) => c.fromAffine(p.toAffine(invertedZs[i])));
|
|
19571
|
+
}
|
|
19572
|
+
function validateW(W, bits) {
|
|
19573
|
+
if (!Number.isSafeInteger(W) || W <= 0 || W > bits)
|
|
19574
|
+
throw new Error("invalid window size, expected [1.." + bits + "], got W=" + W);
|
|
19575
|
+
}
|
|
19576
|
+
function calcWOpts(W, scalarBits) {
|
|
19577
|
+
validateW(W, scalarBits);
|
|
19578
|
+
const windows = Math.ceil(scalarBits / W) + 1;
|
|
19579
|
+
const windowSize = 2 ** (W - 1);
|
|
19580
|
+
const maxNumber = 2 ** W;
|
|
19581
|
+
const mask = bitMask(W);
|
|
19582
|
+
const shiftBy = BigInt(W);
|
|
19583
|
+
return { windows, windowSize, mask, maxNumber, shiftBy };
|
|
19584
|
+
}
|
|
19585
|
+
function calcOffsets(n, window2, wOpts) {
|
|
19586
|
+
const { windowSize, mask, maxNumber, shiftBy } = wOpts;
|
|
19587
|
+
let wbits = Number(n & mask);
|
|
19588
|
+
let nextN = n >> shiftBy;
|
|
19589
|
+
if (wbits > windowSize) {
|
|
19590
|
+
wbits -= maxNumber;
|
|
19591
|
+
nextN += _1n3;
|
|
19592
|
+
}
|
|
19593
|
+
const offsetStart = window2 * windowSize;
|
|
19594
|
+
const offset = offsetStart + Math.abs(wbits) - 1;
|
|
19595
|
+
const isZero = wbits === 0;
|
|
19596
|
+
const isNeg = wbits < 0;
|
|
19597
|
+
const isNegF = window2 % 2 !== 0;
|
|
19598
|
+
const offsetF = offsetStart;
|
|
19599
|
+
return { nextN, offset, isZero, isNeg, isNegF, offsetF };
|
|
19600
|
+
}
|
|
19601
|
+
function validateMSMPoints(points, c) {
|
|
19602
|
+
if (!Array.isArray(points))
|
|
19603
|
+
throw new Error("array expected");
|
|
19604
|
+
points.forEach((p, i) => {
|
|
19605
|
+
if (!(p instanceof c))
|
|
19606
|
+
throw new Error("invalid point at index " + i);
|
|
19607
|
+
});
|
|
19608
|
+
}
|
|
19609
|
+
function validateMSMScalars(scalars, field) {
|
|
19610
|
+
if (!Array.isArray(scalars))
|
|
19611
|
+
throw new Error("array of scalars expected");
|
|
19612
|
+
scalars.forEach((s, i) => {
|
|
19613
|
+
if (!field.isValid(s))
|
|
19614
|
+
throw new Error("invalid scalar at index " + i);
|
|
19615
|
+
});
|
|
19616
|
+
}
|
|
19617
|
+
var pointPrecomputes = /* @__PURE__ */ new WeakMap();
|
|
19618
|
+
var pointWindowSizes = /* @__PURE__ */ new WeakMap();
|
|
19619
|
+
function getW(P) {
|
|
19620
|
+
return pointWindowSizes.get(P) || 1;
|
|
19621
|
+
}
|
|
19622
|
+
function assert0(n) {
|
|
19623
|
+
if (n !== _0n3)
|
|
19624
|
+
throw new Error("invalid wNAF");
|
|
19625
|
+
}
|
|
19626
|
+
var wNAF = class {
|
|
19627
|
+
// Parametrized with a given Point class (not individual point)
|
|
19628
|
+
constructor(Point, bits) {
|
|
19629
|
+
this.BASE = Point.BASE;
|
|
19630
|
+
this.ZERO = Point.ZERO;
|
|
19631
|
+
this.Fn = Point.Fn;
|
|
19632
|
+
this.bits = bits;
|
|
19633
|
+
}
|
|
19634
|
+
// non-const time multiplication ladder
|
|
19635
|
+
_unsafeLadder(elm, n, p = this.ZERO) {
|
|
19636
|
+
let d = elm;
|
|
19637
|
+
while (n > _0n3) {
|
|
19638
|
+
if (n & _1n3)
|
|
19639
|
+
p = p.add(d);
|
|
19640
|
+
d = d.double();
|
|
19641
|
+
n >>= _1n3;
|
|
19642
|
+
}
|
|
19643
|
+
return p;
|
|
19644
|
+
}
|
|
19645
|
+
/**
|
|
19646
|
+
* Creates a wNAF precomputation window. Used for caching.
|
|
19647
|
+
* Default window size is set by `utils.precompute()` and is equal to 8.
|
|
19648
|
+
* Number of precomputed points depends on the curve size:
|
|
19649
|
+
* 2^(𝑊−1) * (Math.ceil(𝑛 / 𝑊) + 1), where:
|
|
19650
|
+
* - 𝑊 is the window size
|
|
19651
|
+
* - 𝑛 is the bitlength of the curve order.
|
|
19652
|
+
* For a 256-bit curve and window size 8, the number of precomputed points is 128 * 33 = 4224.
|
|
19653
|
+
* @param point Point instance
|
|
19654
|
+
* @param W window size
|
|
19655
|
+
* @returns precomputed point tables flattened to a single array
|
|
19656
|
+
*/
|
|
19657
|
+
precomputeWindow(point, W) {
|
|
19658
|
+
const { windows, windowSize } = calcWOpts(W, this.bits);
|
|
19659
|
+
const points = [];
|
|
19660
|
+
let p = point;
|
|
19661
|
+
let base = p;
|
|
19662
|
+
for (let window2 = 0; window2 < windows; window2++) {
|
|
19663
|
+
base = p;
|
|
19664
|
+
points.push(base);
|
|
19665
|
+
for (let i = 1; i < windowSize; i++) {
|
|
19666
|
+
base = base.add(p);
|
|
19667
|
+
points.push(base);
|
|
19668
|
+
}
|
|
19669
|
+
p = base.double();
|
|
19670
|
+
}
|
|
19671
|
+
return points;
|
|
19672
|
+
}
|
|
19673
|
+
/**
|
|
19674
|
+
* Implements ec multiplication using precomputed tables and w-ary non-adjacent form.
|
|
19675
|
+
* More compact implementation:
|
|
19676
|
+
* https://github.com/paulmillr/noble-secp256k1/blob/47cb1669b6e506ad66b35fe7d76132ae97465da2/index.ts#L502-L541
|
|
19677
|
+
* @returns real and fake (for const-time) points
|
|
19678
|
+
*/
|
|
19679
|
+
wNAF(W, precomputes, n) {
|
|
19680
|
+
if (!this.Fn.isValid(n))
|
|
19681
|
+
throw new Error("invalid scalar");
|
|
19682
|
+
let p = this.ZERO;
|
|
19683
|
+
let f = this.BASE;
|
|
19684
|
+
const wo = calcWOpts(W, this.bits);
|
|
19685
|
+
for (let window2 = 0; window2 < wo.windows; window2++) {
|
|
19686
|
+
const { nextN, offset, isZero, isNeg, isNegF, offsetF } = calcOffsets(n, window2, wo);
|
|
19687
|
+
n = nextN;
|
|
19688
|
+
if (isZero) {
|
|
19689
|
+
f = f.add(negateCt(isNegF, precomputes[offsetF]));
|
|
19690
|
+
} else {
|
|
19691
|
+
p = p.add(negateCt(isNeg, precomputes[offset]));
|
|
19692
|
+
}
|
|
19693
|
+
}
|
|
19694
|
+
assert0(n);
|
|
19695
|
+
return { p, f };
|
|
19696
|
+
}
|
|
19697
|
+
/**
|
|
19698
|
+
* Implements ec unsafe (non const-time) multiplication using precomputed tables and w-ary non-adjacent form.
|
|
19699
|
+
* @param acc accumulator point to add result of multiplication
|
|
19700
|
+
* @returns point
|
|
19701
|
+
*/
|
|
19702
|
+
wNAFUnsafe(W, precomputes, n, acc = this.ZERO) {
|
|
19703
|
+
const wo = calcWOpts(W, this.bits);
|
|
19704
|
+
for (let window2 = 0; window2 < wo.windows; window2++) {
|
|
19705
|
+
if (n === _0n3)
|
|
19706
|
+
break;
|
|
19707
|
+
const { nextN, offset, isZero, isNeg } = calcOffsets(n, window2, wo);
|
|
19708
|
+
n = nextN;
|
|
19709
|
+
if (isZero) {
|
|
19710
|
+
continue;
|
|
19711
|
+
} else {
|
|
19712
|
+
const item = precomputes[offset];
|
|
19713
|
+
acc = acc.add(isNeg ? item.negate() : item);
|
|
19714
|
+
}
|
|
19715
|
+
}
|
|
19716
|
+
assert0(n);
|
|
19717
|
+
return acc;
|
|
19718
|
+
}
|
|
19719
|
+
getPrecomputes(W, point, transform) {
|
|
19720
|
+
let comp = pointPrecomputes.get(point);
|
|
19721
|
+
if (!comp) {
|
|
19722
|
+
comp = this.precomputeWindow(point, W);
|
|
19723
|
+
if (W !== 1) {
|
|
19724
|
+
if (typeof transform === "function")
|
|
19725
|
+
comp = transform(comp);
|
|
19726
|
+
pointPrecomputes.set(point, comp);
|
|
19727
|
+
}
|
|
19728
|
+
}
|
|
19729
|
+
return comp;
|
|
19730
|
+
}
|
|
19731
|
+
cached(point, scalar, transform) {
|
|
19732
|
+
const W = getW(point);
|
|
19733
|
+
return this.wNAF(W, this.getPrecomputes(W, point, transform), scalar);
|
|
19734
|
+
}
|
|
19735
|
+
unsafe(point, scalar, transform, prev) {
|
|
19736
|
+
const W = getW(point);
|
|
19737
|
+
if (W === 1)
|
|
19738
|
+
return this._unsafeLadder(point, scalar, prev);
|
|
19739
|
+
return this.wNAFUnsafe(W, this.getPrecomputes(W, point, transform), scalar, prev);
|
|
19740
|
+
}
|
|
19741
|
+
// We calculate precomputes for elliptic curve point multiplication
|
|
19742
|
+
// using windowed method. This specifies window size and
|
|
19743
|
+
// stores precomputed values. Usually only base point would be precomputed.
|
|
19744
|
+
createCache(P, W) {
|
|
19745
|
+
validateW(W, this.bits);
|
|
19746
|
+
pointWindowSizes.set(P, W);
|
|
19747
|
+
pointPrecomputes.delete(P);
|
|
19748
|
+
}
|
|
19749
|
+
hasCache(elm) {
|
|
19750
|
+
return getW(elm) !== 1;
|
|
19751
|
+
}
|
|
19752
|
+
};
|
|
19753
|
+
function pippenger(c, fieldN, points, scalars) {
|
|
19754
|
+
validateMSMPoints(points, c);
|
|
19755
|
+
validateMSMScalars(scalars, fieldN);
|
|
19756
|
+
const plength = points.length;
|
|
19757
|
+
const slength = scalars.length;
|
|
19758
|
+
if (plength !== slength)
|
|
19759
|
+
throw new Error("arrays of points and scalars must have equal length");
|
|
19760
|
+
const zero = c.ZERO;
|
|
19761
|
+
const wbits = bitLen(BigInt(plength));
|
|
19762
|
+
let windowSize = 1;
|
|
19763
|
+
if (wbits > 12)
|
|
19764
|
+
windowSize = wbits - 3;
|
|
19765
|
+
else if (wbits > 4)
|
|
19766
|
+
windowSize = wbits - 2;
|
|
19767
|
+
else if (wbits > 0)
|
|
19768
|
+
windowSize = 2;
|
|
19769
|
+
const MASK = bitMask(windowSize);
|
|
19770
|
+
const buckets = new Array(Number(MASK) + 1).fill(zero);
|
|
19771
|
+
const lastBits = Math.floor((fieldN.BITS - 1) / windowSize) * windowSize;
|
|
19772
|
+
let sum = zero;
|
|
19773
|
+
for (let i = lastBits; i >= 0; i -= windowSize) {
|
|
19774
|
+
buckets.fill(zero);
|
|
19775
|
+
for (let j = 0; j < slength; j++) {
|
|
19776
|
+
const scalar = scalars[j];
|
|
19777
|
+
const wbits2 = Number(scalar >> BigInt(i) & MASK);
|
|
19778
|
+
buckets[wbits2] = buckets[wbits2].add(points[j]);
|
|
19779
|
+
}
|
|
19780
|
+
let resI = zero;
|
|
19781
|
+
for (let j = buckets.length - 1, sumI = zero; j > 0; j--) {
|
|
19782
|
+
sumI = sumI.add(buckets[j]);
|
|
19783
|
+
resI = resI.add(sumI);
|
|
19784
|
+
}
|
|
19785
|
+
sum = sum.add(resI);
|
|
19786
|
+
if (i !== 0)
|
|
19787
|
+
for (let j = 0; j < windowSize; j++)
|
|
19788
|
+
sum = sum.double();
|
|
19789
|
+
}
|
|
19790
|
+
return sum;
|
|
19791
|
+
}
|
|
19792
|
+
function createField(order, field, isLE) {
|
|
19793
|
+
if (field) {
|
|
19794
|
+
if (field.ORDER !== order)
|
|
19795
|
+
throw new Error("Field.ORDER must match order: Fp == p, Fn == n");
|
|
19796
|
+
validateField(field);
|
|
19797
|
+
return field;
|
|
19798
|
+
} else {
|
|
19799
|
+
return Field(order, { isLE });
|
|
19800
|
+
}
|
|
19801
|
+
}
|
|
19802
|
+
function _createCurveFields(type, CURVE, curveOpts = {}, FpFnLE) {
|
|
19803
|
+
if (FpFnLE === void 0)
|
|
19804
|
+
FpFnLE = type === "edwards";
|
|
19805
|
+
if (!CURVE || typeof CURVE !== "object")
|
|
19806
|
+
throw new Error(`expected valid ${type} CURVE object`);
|
|
19807
|
+
for (const p of ["p", "n", "h"]) {
|
|
19808
|
+
const val = CURVE[p];
|
|
19809
|
+
if (!(typeof val === "bigint" && val > _0n3))
|
|
19810
|
+
throw new Error(`CURVE.${p} must be positive bigint`);
|
|
19811
|
+
}
|
|
19812
|
+
const Fp2 = createField(CURVE.p, curveOpts.Fp, FpFnLE);
|
|
19813
|
+
const Fn2 = createField(CURVE.n, curveOpts.Fn, FpFnLE);
|
|
19814
|
+
const _b = type === "weierstrass" ? "b" : "d";
|
|
19815
|
+
const params = ["Gx", "Gy", "a", _b];
|
|
19816
|
+
for (const p of params) {
|
|
19817
|
+
if (!Fp2.isValid(CURVE[p]))
|
|
19818
|
+
throw new Error(`CURVE.${p} must be valid field element of CURVE.Fp`);
|
|
19819
|
+
}
|
|
19820
|
+
CURVE = Object.freeze(Object.assign({}, CURVE));
|
|
19821
|
+
return { CURVE, Fp: Fp2, Fn: Fn2 };
|
|
19822
|
+
}
|
|
19823
|
+
|
|
19824
|
+
// ../mmt-manifest-verify/node_modules/@noble/curves/esm/abstract/edwards.js
|
|
19825
|
+
var _0n4 = BigInt(0);
|
|
19826
|
+
var _1n4 = BigInt(1);
|
|
19827
|
+
var _2n2 = BigInt(2);
|
|
19828
|
+
var _8n2 = BigInt(8);
|
|
19829
|
+
function isEdValidXY(Fp2, CURVE, x, y) {
|
|
19830
|
+
const x2 = Fp2.sqr(x);
|
|
19831
|
+
const y2 = Fp2.sqr(y);
|
|
19832
|
+
const left = Fp2.add(Fp2.mul(CURVE.a, x2), y2);
|
|
19833
|
+
const right = Fp2.add(Fp2.ONE, Fp2.mul(CURVE.d, Fp2.mul(x2, y2)));
|
|
19834
|
+
return Fp2.eql(left, right);
|
|
19835
|
+
}
|
|
19836
|
+
function edwards(params, extraOpts = {}) {
|
|
19837
|
+
const validated = _createCurveFields("edwards", params, extraOpts, extraOpts.FpFnLE);
|
|
19838
|
+
const { Fp: Fp2, Fn: Fn2 } = validated;
|
|
19839
|
+
let CURVE = validated.CURVE;
|
|
19840
|
+
const { h: cofactor } = CURVE;
|
|
19841
|
+
_validateObject(extraOpts, {}, { uvRatio: "function" });
|
|
19842
|
+
const MASK = _2n2 << BigInt(Fn2.BYTES * 8) - _1n4;
|
|
19843
|
+
const modP = (n) => Fp2.create(n);
|
|
19844
|
+
const uvRatio2 = extraOpts.uvRatio || ((u, v) => {
|
|
19845
|
+
try {
|
|
19846
|
+
return { isValid: true, value: Fp2.sqrt(Fp2.div(u, v)) };
|
|
19847
|
+
} catch (e) {
|
|
19848
|
+
return { isValid: false, value: _0n4 };
|
|
19849
|
+
}
|
|
19850
|
+
});
|
|
19851
|
+
if (!isEdValidXY(Fp2, CURVE, CURVE.Gx, CURVE.Gy))
|
|
19852
|
+
throw new Error("bad curve params: generator point");
|
|
19853
|
+
function acoord(title, n, banZero = false) {
|
|
19854
|
+
const min = banZero ? _1n4 : _0n4;
|
|
19855
|
+
aInRange("coordinate " + title, n, min, MASK);
|
|
19856
|
+
return n;
|
|
19857
|
+
}
|
|
19858
|
+
function aextpoint(other) {
|
|
19859
|
+
if (!(other instanceof Point))
|
|
19860
|
+
throw new Error("ExtendedPoint expected");
|
|
19861
|
+
}
|
|
19862
|
+
const toAffineMemo = memoized((p, iz) => {
|
|
19863
|
+
const { X, Y, Z } = p;
|
|
19864
|
+
const is0 = p.is0();
|
|
19865
|
+
if (iz == null)
|
|
19866
|
+
iz = is0 ? _8n2 : Fp2.inv(Z);
|
|
19867
|
+
const x = modP(X * iz);
|
|
19868
|
+
const y = modP(Y * iz);
|
|
19869
|
+
const zz = Fp2.mul(Z, iz);
|
|
19870
|
+
if (is0)
|
|
19871
|
+
return { x: _0n4, y: _1n4 };
|
|
19872
|
+
if (zz !== _1n4)
|
|
19873
|
+
throw new Error("invZ was invalid");
|
|
19874
|
+
return { x, y };
|
|
19875
|
+
});
|
|
19876
|
+
const assertValidMemo = memoized((p) => {
|
|
19877
|
+
const { a, d } = CURVE;
|
|
19878
|
+
if (p.is0())
|
|
19879
|
+
throw new Error("bad point: ZERO");
|
|
19880
|
+
const { X, Y, Z, T } = p;
|
|
19881
|
+
const X2 = modP(X * X);
|
|
19882
|
+
const Y2 = modP(Y * Y);
|
|
19883
|
+
const Z2 = modP(Z * Z);
|
|
19884
|
+
const Z4 = modP(Z2 * Z2);
|
|
19885
|
+
const aX2 = modP(X2 * a);
|
|
19886
|
+
const left = modP(Z2 * modP(aX2 + Y2));
|
|
19887
|
+
const right = modP(Z4 + modP(d * modP(X2 * Y2)));
|
|
19888
|
+
if (left !== right)
|
|
19889
|
+
throw new Error("bad point: equation left != right (1)");
|
|
19890
|
+
const XY = modP(X * Y);
|
|
19891
|
+
const ZT = modP(Z * T);
|
|
19892
|
+
if (XY !== ZT)
|
|
19893
|
+
throw new Error("bad point: equation left != right (2)");
|
|
19894
|
+
return true;
|
|
19895
|
+
});
|
|
19896
|
+
class Point {
|
|
19897
|
+
constructor(X, Y, Z, T) {
|
|
19898
|
+
this.X = acoord("x", X);
|
|
19899
|
+
this.Y = acoord("y", Y);
|
|
19900
|
+
this.Z = acoord("z", Z, true);
|
|
19901
|
+
this.T = acoord("t", T);
|
|
19902
|
+
Object.freeze(this);
|
|
19903
|
+
}
|
|
19904
|
+
static CURVE() {
|
|
19905
|
+
return CURVE;
|
|
19906
|
+
}
|
|
19907
|
+
static fromAffine(p) {
|
|
19908
|
+
if (p instanceof Point)
|
|
19909
|
+
throw new Error("extended point not allowed");
|
|
19910
|
+
const { x, y } = p || {};
|
|
19911
|
+
acoord("x", x);
|
|
19912
|
+
acoord("y", y);
|
|
19913
|
+
return new Point(x, y, _1n4, modP(x * y));
|
|
19914
|
+
}
|
|
19915
|
+
// Uses algo from RFC8032 5.1.3.
|
|
19916
|
+
static fromBytes(bytes, zip215 = false) {
|
|
19917
|
+
const len = Fp2.BYTES;
|
|
19918
|
+
const { a, d } = CURVE;
|
|
19919
|
+
bytes = copyBytes(_abytes2(bytes, len, "point"));
|
|
19920
|
+
_abool2(zip215, "zip215");
|
|
19921
|
+
const normed = copyBytes(bytes);
|
|
19922
|
+
const lastByte = bytes[len - 1];
|
|
19923
|
+
normed[len - 1] = lastByte & ~128;
|
|
19924
|
+
const y = bytesToNumberLE(normed);
|
|
19925
|
+
const max = zip215 ? MASK : Fp2.ORDER;
|
|
19926
|
+
aInRange("point.y", y, _0n4, max);
|
|
19927
|
+
const y2 = modP(y * y);
|
|
19928
|
+
const u = modP(y2 - _1n4);
|
|
19929
|
+
const v = modP(d * y2 - a);
|
|
19930
|
+
let { isValid, value: x } = uvRatio2(u, v);
|
|
19931
|
+
if (!isValid)
|
|
19932
|
+
throw new Error("bad point: invalid y coordinate");
|
|
19933
|
+
const isXOdd = (x & _1n4) === _1n4;
|
|
19934
|
+
const isLastByteOdd = (lastByte & 128) !== 0;
|
|
19935
|
+
if (!zip215 && x === _0n4 && isLastByteOdd)
|
|
19936
|
+
throw new Error("bad point: x=0 and x_0=1");
|
|
19937
|
+
if (isLastByteOdd !== isXOdd)
|
|
19938
|
+
x = modP(-x);
|
|
19939
|
+
return Point.fromAffine({ x, y });
|
|
19940
|
+
}
|
|
19941
|
+
static fromHex(bytes, zip215 = false) {
|
|
19942
|
+
return Point.fromBytes(ensureBytes("point", bytes), zip215);
|
|
19943
|
+
}
|
|
19944
|
+
get x() {
|
|
19945
|
+
return this.toAffine().x;
|
|
19946
|
+
}
|
|
19947
|
+
get y() {
|
|
19948
|
+
return this.toAffine().y;
|
|
19949
|
+
}
|
|
19950
|
+
precompute(windowSize = 8, isLazy = true) {
|
|
19951
|
+
wnaf.createCache(this, windowSize);
|
|
19952
|
+
if (!isLazy)
|
|
19953
|
+
this.multiply(_2n2);
|
|
19954
|
+
return this;
|
|
19955
|
+
}
|
|
19956
|
+
// Useful in fromAffine() - not for fromBytes(), which always created valid points.
|
|
19957
|
+
assertValidity() {
|
|
19958
|
+
assertValidMemo(this);
|
|
19959
|
+
}
|
|
19960
|
+
// Compare one point to another.
|
|
19961
|
+
equals(other) {
|
|
19962
|
+
aextpoint(other);
|
|
19963
|
+
const { X: X1, Y: Y1, Z: Z1 } = this;
|
|
19964
|
+
const { X: X2, Y: Y2, Z: Z2 } = other;
|
|
19965
|
+
const X1Z2 = modP(X1 * Z2);
|
|
19966
|
+
const X2Z1 = modP(X2 * Z1);
|
|
19967
|
+
const Y1Z2 = modP(Y1 * Z2);
|
|
19968
|
+
const Y2Z1 = modP(Y2 * Z1);
|
|
19969
|
+
return X1Z2 === X2Z1 && Y1Z2 === Y2Z1;
|
|
19970
|
+
}
|
|
19971
|
+
is0() {
|
|
19972
|
+
return this.equals(Point.ZERO);
|
|
19973
|
+
}
|
|
19974
|
+
negate() {
|
|
19975
|
+
return new Point(modP(-this.X), this.Y, this.Z, modP(-this.T));
|
|
19976
|
+
}
|
|
19977
|
+
// Fast algo for doubling Extended Point.
|
|
19978
|
+
// https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#doubling-dbl-2008-hwcd
|
|
19979
|
+
// Cost: 4M + 4S + 1*a + 6add + 1*2.
|
|
19980
|
+
double() {
|
|
19981
|
+
const { a } = CURVE;
|
|
19982
|
+
const { X: X1, Y: Y1, Z: Z1 } = this;
|
|
19983
|
+
const A = modP(X1 * X1);
|
|
19984
|
+
const B = modP(Y1 * Y1);
|
|
19985
|
+
const C = modP(_2n2 * modP(Z1 * Z1));
|
|
19986
|
+
const D = modP(a * A);
|
|
19987
|
+
const x1y1 = X1 + Y1;
|
|
19988
|
+
const E = modP(modP(x1y1 * x1y1) - A - B);
|
|
19989
|
+
const G = D + B;
|
|
19990
|
+
const F = G - C;
|
|
19991
|
+
const H = D - B;
|
|
19992
|
+
const X3 = modP(E * F);
|
|
19993
|
+
const Y3 = modP(G * H);
|
|
19994
|
+
const T3 = modP(E * H);
|
|
19995
|
+
const Z3 = modP(F * G);
|
|
19996
|
+
return new Point(X3, Y3, Z3, T3);
|
|
19997
|
+
}
|
|
19998
|
+
// Fast algo for adding 2 Extended Points.
|
|
19999
|
+
// https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#addition-add-2008-hwcd
|
|
20000
|
+
// Cost: 9M + 1*a + 1*d + 7add.
|
|
20001
|
+
add(other) {
|
|
20002
|
+
aextpoint(other);
|
|
20003
|
+
const { a, d } = CURVE;
|
|
20004
|
+
const { X: X1, Y: Y1, Z: Z1, T: T1 } = this;
|
|
20005
|
+
const { X: X2, Y: Y2, Z: Z2, T: T2 } = other;
|
|
20006
|
+
const A = modP(X1 * X2);
|
|
20007
|
+
const B = modP(Y1 * Y2);
|
|
20008
|
+
const C = modP(T1 * d * T2);
|
|
20009
|
+
const D = modP(Z1 * Z2);
|
|
20010
|
+
const E = modP((X1 + Y1) * (X2 + Y2) - A - B);
|
|
20011
|
+
const F = D - C;
|
|
20012
|
+
const G = D + C;
|
|
20013
|
+
const H = modP(B - a * A);
|
|
20014
|
+
const X3 = modP(E * F);
|
|
20015
|
+
const Y3 = modP(G * H);
|
|
20016
|
+
const T3 = modP(E * H);
|
|
20017
|
+
const Z3 = modP(F * G);
|
|
20018
|
+
return new Point(X3, Y3, Z3, T3);
|
|
20019
|
+
}
|
|
20020
|
+
subtract(other) {
|
|
20021
|
+
return this.add(other.negate());
|
|
20022
|
+
}
|
|
20023
|
+
// Constant-time multiplication.
|
|
20024
|
+
multiply(scalar) {
|
|
20025
|
+
if (!Fn2.isValidNot0(scalar))
|
|
20026
|
+
throw new Error("invalid scalar: expected 1 <= sc < curve.n");
|
|
20027
|
+
const { p, f } = wnaf.cached(this, scalar, (p2) => normalizeZ(Point, p2));
|
|
20028
|
+
return normalizeZ(Point, [p, f])[0];
|
|
20029
|
+
}
|
|
20030
|
+
// Non-constant-time multiplication. Uses double-and-add algorithm.
|
|
20031
|
+
// It's faster, but should only be used when you don't care about
|
|
20032
|
+
// an exposed private key e.g. sig verification.
|
|
20033
|
+
// Does NOT allow scalars higher than CURVE.n.
|
|
20034
|
+
// Accepts optional accumulator to merge with multiply (important for sparse scalars)
|
|
20035
|
+
multiplyUnsafe(scalar, acc = Point.ZERO) {
|
|
20036
|
+
if (!Fn2.isValid(scalar))
|
|
20037
|
+
throw new Error("invalid scalar: expected 0 <= sc < curve.n");
|
|
20038
|
+
if (scalar === _0n4)
|
|
20039
|
+
return Point.ZERO;
|
|
20040
|
+
if (this.is0() || scalar === _1n4)
|
|
20041
|
+
return this;
|
|
20042
|
+
return wnaf.unsafe(this, scalar, (p) => normalizeZ(Point, p), acc);
|
|
20043
|
+
}
|
|
20044
|
+
// Checks if point is of small order.
|
|
20045
|
+
// If you add something to small order point, you will have "dirty"
|
|
20046
|
+
// point with torsion component.
|
|
20047
|
+
// Multiplies point by cofactor and checks if the result is 0.
|
|
20048
|
+
isSmallOrder() {
|
|
20049
|
+
return this.multiplyUnsafe(cofactor).is0();
|
|
20050
|
+
}
|
|
20051
|
+
// Multiplies point by curve order and checks if the result is 0.
|
|
20052
|
+
// Returns `false` is the point is dirty.
|
|
20053
|
+
isTorsionFree() {
|
|
20054
|
+
return wnaf.unsafe(this, CURVE.n).is0();
|
|
20055
|
+
}
|
|
20056
|
+
// Converts Extended point to default (x, y) coordinates.
|
|
20057
|
+
// Can accept precomputed Z^-1 - for example, from invertBatch.
|
|
20058
|
+
toAffine(invertedZ) {
|
|
20059
|
+
return toAffineMemo(this, invertedZ);
|
|
20060
|
+
}
|
|
20061
|
+
clearCofactor() {
|
|
20062
|
+
if (cofactor === _1n4)
|
|
20063
|
+
return this;
|
|
20064
|
+
return this.multiplyUnsafe(cofactor);
|
|
20065
|
+
}
|
|
20066
|
+
toBytes() {
|
|
20067
|
+
const { x, y } = this.toAffine();
|
|
20068
|
+
const bytes = Fp2.toBytes(y);
|
|
20069
|
+
bytes[bytes.length - 1] |= x & _1n4 ? 128 : 0;
|
|
20070
|
+
return bytes;
|
|
20071
|
+
}
|
|
20072
|
+
toHex() {
|
|
20073
|
+
return bytesToHex(this.toBytes());
|
|
20074
|
+
}
|
|
20075
|
+
toString() {
|
|
20076
|
+
return `<Point ${this.is0() ? "ZERO" : this.toHex()}>`;
|
|
20077
|
+
}
|
|
20078
|
+
// TODO: remove
|
|
20079
|
+
get ex() {
|
|
20080
|
+
return this.X;
|
|
20081
|
+
}
|
|
20082
|
+
get ey() {
|
|
20083
|
+
return this.Y;
|
|
20084
|
+
}
|
|
20085
|
+
get ez() {
|
|
20086
|
+
return this.Z;
|
|
20087
|
+
}
|
|
20088
|
+
get et() {
|
|
20089
|
+
return this.T;
|
|
20090
|
+
}
|
|
20091
|
+
static normalizeZ(points) {
|
|
20092
|
+
return normalizeZ(Point, points);
|
|
20093
|
+
}
|
|
20094
|
+
static msm(points, scalars) {
|
|
20095
|
+
return pippenger(Point, Fn2, points, scalars);
|
|
20096
|
+
}
|
|
20097
|
+
_setWindowSize(windowSize) {
|
|
20098
|
+
this.precompute(windowSize);
|
|
20099
|
+
}
|
|
20100
|
+
toRawBytes() {
|
|
20101
|
+
return this.toBytes();
|
|
20102
|
+
}
|
|
20103
|
+
}
|
|
20104
|
+
Point.BASE = new Point(CURVE.Gx, CURVE.Gy, _1n4, modP(CURVE.Gx * CURVE.Gy));
|
|
20105
|
+
Point.ZERO = new Point(_0n4, _1n4, _1n4, _0n4);
|
|
20106
|
+
Point.Fp = Fp2;
|
|
20107
|
+
Point.Fn = Fn2;
|
|
20108
|
+
const wnaf = new wNAF(Point, Fn2.BITS);
|
|
20109
|
+
Point.BASE.precompute(8);
|
|
20110
|
+
return Point;
|
|
20111
|
+
}
|
|
20112
|
+
var PrimeEdwardsPoint = class {
|
|
20113
|
+
constructor(ep) {
|
|
20114
|
+
this.ep = ep;
|
|
20115
|
+
}
|
|
20116
|
+
// Static methods that must be implemented by subclasses
|
|
20117
|
+
static fromBytes(_bytes) {
|
|
20118
|
+
notImplemented();
|
|
20119
|
+
}
|
|
20120
|
+
static fromHex(_hex) {
|
|
20121
|
+
notImplemented();
|
|
20122
|
+
}
|
|
20123
|
+
get x() {
|
|
20124
|
+
return this.toAffine().x;
|
|
20125
|
+
}
|
|
20126
|
+
get y() {
|
|
20127
|
+
return this.toAffine().y;
|
|
20128
|
+
}
|
|
20129
|
+
// Common implementations
|
|
20130
|
+
clearCofactor() {
|
|
20131
|
+
return this;
|
|
20132
|
+
}
|
|
20133
|
+
assertValidity() {
|
|
20134
|
+
this.ep.assertValidity();
|
|
20135
|
+
}
|
|
20136
|
+
toAffine(invertedZ) {
|
|
20137
|
+
return this.ep.toAffine(invertedZ);
|
|
20138
|
+
}
|
|
20139
|
+
toHex() {
|
|
20140
|
+
return bytesToHex(this.toBytes());
|
|
20141
|
+
}
|
|
20142
|
+
toString() {
|
|
20143
|
+
return this.toHex();
|
|
20144
|
+
}
|
|
20145
|
+
isTorsionFree() {
|
|
20146
|
+
return true;
|
|
20147
|
+
}
|
|
20148
|
+
isSmallOrder() {
|
|
20149
|
+
return false;
|
|
20150
|
+
}
|
|
20151
|
+
add(other) {
|
|
20152
|
+
this.assertSame(other);
|
|
20153
|
+
return this.init(this.ep.add(other.ep));
|
|
20154
|
+
}
|
|
20155
|
+
subtract(other) {
|
|
20156
|
+
this.assertSame(other);
|
|
20157
|
+
return this.init(this.ep.subtract(other.ep));
|
|
20158
|
+
}
|
|
20159
|
+
multiply(scalar) {
|
|
20160
|
+
return this.init(this.ep.multiply(scalar));
|
|
20161
|
+
}
|
|
20162
|
+
multiplyUnsafe(scalar) {
|
|
20163
|
+
return this.init(this.ep.multiplyUnsafe(scalar));
|
|
20164
|
+
}
|
|
20165
|
+
double() {
|
|
20166
|
+
return this.init(this.ep.double());
|
|
20167
|
+
}
|
|
20168
|
+
negate() {
|
|
20169
|
+
return this.init(this.ep.negate());
|
|
20170
|
+
}
|
|
20171
|
+
precompute(windowSize, isLazy) {
|
|
20172
|
+
return this.init(this.ep.precompute(windowSize, isLazy));
|
|
20173
|
+
}
|
|
20174
|
+
/** @deprecated use `toBytes` */
|
|
20175
|
+
toRawBytes() {
|
|
20176
|
+
return this.toBytes();
|
|
20177
|
+
}
|
|
20178
|
+
};
|
|
20179
|
+
function eddsa(Point, cHash, eddsaOpts = {}) {
|
|
20180
|
+
if (typeof cHash !== "function")
|
|
20181
|
+
throw new Error('"hash" function param is required');
|
|
20182
|
+
_validateObject(eddsaOpts, {}, {
|
|
20183
|
+
adjustScalarBytes: "function",
|
|
20184
|
+
randomBytes: "function",
|
|
20185
|
+
domain: "function",
|
|
20186
|
+
prehash: "function",
|
|
20187
|
+
mapToCurve: "function"
|
|
20188
|
+
});
|
|
20189
|
+
const { prehash } = eddsaOpts;
|
|
20190
|
+
const { BASE, Fp: Fp2, Fn: Fn2 } = Point;
|
|
20191
|
+
const randomBytes2 = eddsaOpts.randomBytes || randomBytes;
|
|
20192
|
+
const adjustScalarBytes2 = eddsaOpts.adjustScalarBytes || ((bytes) => bytes);
|
|
20193
|
+
const domain = eddsaOpts.domain || ((data, ctx, phflag) => {
|
|
20194
|
+
_abool2(phflag, "phflag");
|
|
20195
|
+
if (ctx.length || phflag)
|
|
20196
|
+
throw new Error("Contexts/pre-hash are not supported");
|
|
20197
|
+
return data;
|
|
20198
|
+
});
|
|
20199
|
+
function modN_LE(hash) {
|
|
20200
|
+
return Fn2.create(bytesToNumberLE(hash));
|
|
20201
|
+
}
|
|
20202
|
+
function getPrivateScalar(key) {
|
|
20203
|
+
const len = lengths.secretKey;
|
|
20204
|
+
key = ensureBytes("private key", key, len);
|
|
20205
|
+
const hashed = ensureBytes("hashed private key", cHash(key), 2 * len);
|
|
20206
|
+
const head = adjustScalarBytes2(hashed.slice(0, len));
|
|
20207
|
+
const prefix = hashed.slice(len, 2 * len);
|
|
20208
|
+
const scalar = modN_LE(head);
|
|
20209
|
+
return { head, prefix, scalar };
|
|
20210
|
+
}
|
|
20211
|
+
function getExtendedPublicKey(secretKey) {
|
|
20212
|
+
const { head, prefix, scalar } = getPrivateScalar(secretKey);
|
|
20213
|
+
const point = BASE.multiply(scalar);
|
|
20214
|
+
const pointBytes = point.toBytes();
|
|
20215
|
+
return { head, prefix, scalar, point, pointBytes };
|
|
20216
|
+
}
|
|
20217
|
+
function getPublicKey(secretKey) {
|
|
20218
|
+
return getExtendedPublicKey(secretKey).pointBytes;
|
|
20219
|
+
}
|
|
20220
|
+
function hashDomainToScalar(context = Uint8Array.of(), ...msgs) {
|
|
20221
|
+
const msg = concatBytes(...msgs);
|
|
20222
|
+
return modN_LE(cHash(domain(msg, ensureBytes("context", context), !!prehash)));
|
|
20223
|
+
}
|
|
20224
|
+
function sign(msg, secretKey, options = {}) {
|
|
20225
|
+
msg = ensureBytes("message", msg);
|
|
20226
|
+
if (prehash)
|
|
20227
|
+
msg = prehash(msg);
|
|
20228
|
+
const { prefix, scalar, pointBytes } = getExtendedPublicKey(secretKey);
|
|
20229
|
+
const r = hashDomainToScalar(options.context, prefix, msg);
|
|
20230
|
+
const R = BASE.multiply(r).toBytes();
|
|
20231
|
+
const k = hashDomainToScalar(options.context, R, pointBytes, msg);
|
|
20232
|
+
const s = Fn2.create(r + k * scalar);
|
|
20233
|
+
if (!Fn2.isValid(s))
|
|
20234
|
+
throw new Error("sign failed: invalid s");
|
|
20235
|
+
const rs = concatBytes(R, Fn2.toBytes(s));
|
|
20236
|
+
return _abytes2(rs, lengths.signature, "result");
|
|
20237
|
+
}
|
|
20238
|
+
const verifyOpts = { zip215: true };
|
|
20239
|
+
function verify(sig, msg, publicKey, options = verifyOpts) {
|
|
20240
|
+
const { context, zip215 } = options;
|
|
20241
|
+
const len = lengths.signature;
|
|
20242
|
+
sig = ensureBytes("signature", sig, len);
|
|
20243
|
+
msg = ensureBytes("message", msg);
|
|
20244
|
+
publicKey = ensureBytes("publicKey", publicKey, lengths.publicKey);
|
|
20245
|
+
if (zip215 !== void 0)
|
|
20246
|
+
_abool2(zip215, "zip215");
|
|
20247
|
+
if (prehash)
|
|
20248
|
+
msg = prehash(msg);
|
|
20249
|
+
const mid = len / 2;
|
|
20250
|
+
const r = sig.subarray(0, mid);
|
|
20251
|
+
const s = bytesToNumberLE(sig.subarray(mid, len));
|
|
20252
|
+
let A, R, SB;
|
|
20253
|
+
try {
|
|
20254
|
+
A = Point.fromBytes(publicKey, zip215);
|
|
20255
|
+
R = Point.fromBytes(r, zip215);
|
|
20256
|
+
SB = BASE.multiplyUnsafe(s);
|
|
20257
|
+
} catch (error) {
|
|
20258
|
+
return false;
|
|
20259
|
+
}
|
|
20260
|
+
if (!zip215 && A.isSmallOrder())
|
|
20261
|
+
return false;
|
|
20262
|
+
const k = hashDomainToScalar(context, R.toBytes(), A.toBytes(), msg);
|
|
20263
|
+
const RkA = R.add(A.multiplyUnsafe(k));
|
|
20264
|
+
return RkA.subtract(SB).clearCofactor().is0();
|
|
20265
|
+
}
|
|
20266
|
+
const _size = Fp2.BYTES;
|
|
20267
|
+
const lengths = {
|
|
20268
|
+
secretKey: _size,
|
|
20269
|
+
publicKey: _size,
|
|
20270
|
+
signature: 2 * _size,
|
|
20271
|
+
seed: _size
|
|
20272
|
+
};
|
|
20273
|
+
function randomSecretKey(seed = randomBytes2(lengths.seed)) {
|
|
20274
|
+
return _abytes2(seed, lengths.seed, "seed");
|
|
20275
|
+
}
|
|
20276
|
+
function keygen(seed) {
|
|
20277
|
+
const secretKey = utils.randomSecretKey(seed);
|
|
20278
|
+
return { secretKey, publicKey: getPublicKey(secretKey) };
|
|
20279
|
+
}
|
|
20280
|
+
function isValidSecretKey(key) {
|
|
20281
|
+
return isBytes(key) && key.length === Fn2.BYTES;
|
|
20282
|
+
}
|
|
20283
|
+
function isValidPublicKey(key, zip215) {
|
|
20284
|
+
try {
|
|
20285
|
+
return !!Point.fromBytes(key, zip215);
|
|
20286
|
+
} catch (error) {
|
|
20287
|
+
return false;
|
|
20288
|
+
}
|
|
20289
|
+
}
|
|
20290
|
+
const utils = {
|
|
20291
|
+
getExtendedPublicKey,
|
|
20292
|
+
randomSecretKey,
|
|
20293
|
+
isValidSecretKey,
|
|
20294
|
+
isValidPublicKey,
|
|
20295
|
+
/**
|
|
20296
|
+
* Converts ed public key to x public key. Uses formula:
|
|
20297
|
+
* - ed25519:
|
|
20298
|
+
* - `(u, v) = ((1+y)/(1-y), sqrt(-486664)*u/x)`
|
|
20299
|
+
* - `(x, y) = (sqrt(-486664)*u/v, (u-1)/(u+1))`
|
|
20300
|
+
* - ed448:
|
|
20301
|
+
* - `(u, v) = ((y-1)/(y+1), sqrt(156324)*u/x)`
|
|
20302
|
+
* - `(x, y) = (sqrt(156324)*u/v, (1+u)/(1-u))`
|
|
20303
|
+
*/
|
|
20304
|
+
toMontgomery(publicKey) {
|
|
20305
|
+
const { y } = Point.fromBytes(publicKey);
|
|
20306
|
+
const size = lengths.publicKey;
|
|
20307
|
+
const is25519 = size === 32;
|
|
20308
|
+
if (!is25519 && size !== 57)
|
|
20309
|
+
throw new Error("only defined for 25519 and 448");
|
|
20310
|
+
const u = is25519 ? Fp2.div(_1n4 + y, _1n4 - y) : Fp2.div(y - _1n4, y + _1n4);
|
|
20311
|
+
return Fp2.toBytes(u);
|
|
20312
|
+
},
|
|
20313
|
+
toMontgomerySecret(secretKey) {
|
|
20314
|
+
const size = lengths.secretKey;
|
|
20315
|
+
_abytes2(secretKey, size);
|
|
20316
|
+
const hashed = cHash(secretKey.subarray(0, size));
|
|
20317
|
+
return adjustScalarBytes2(hashed).subarray(0, size);
|
|
20318
|
+
},
|
|
20319
|
+
/** @deprecated */
|
|
20320
|
+
randomPrivateKey: randomSecretKey,
|
|
20321
|
+
/** @deprecated */
|
|
20322
|
+
precompute(windowSize = 8, point = Point.BASE) {
|
|
20323
|
+
return point.precompute(windowSize, false);
|
|
20324
|
+
}
|
|
20325
|
+
};
|
|
20326
|
+
return Object.freeze({
|
|
20327
|
+
keygen,
|
|
20328
|
+
getPublicKey,
|
|
20329
|
+
sign,
|
|
20330
|
+
verify,
|
|
20331
|
+
utils,
|
|
20332
|
+
Point,
|
|
20333
|
+
lengths
|
|
20334
|
+
});
|
|
20335
|
+
}
|
|
20336
|
+
function _eddsa_legacy_opts_to_new(c) {
|
|
20337
|
+
const CURVE = {
|
|
20338
|
+
a: c.a,
|
|
20339
|
+
d: c.d,
|
|
20340
|
+
p: c.Fp.ORDER,
|
|
20341
|
+
n: c.n,
|
|
20342
|
+
h: c.h,
|
|
20343
|
+
Gx: c.Gx,
|
|
20344
|
+
Gy: c.Gy
|
|
20345
|
+
};
|
|
20346
|
+
const Fp2 = c.Fp;
|
|
20347
|
+
const Fn2 = Field(CURVE.n, c.nBitLength, true);
|
|
20348
|
+
const curveOpts = { Fp: Fp2, Fn: Fn2, uvRatio: c.uvRatio };
|
|
20349
|
+
const eddsaOpts = {
|
|
20350
|
+
randomBytes: c.randomBytes,
|
|
20351
|
+
adjustScalarBytes: c.adjustScalarBytes,
|
|
20352
|
+
domain: c.domain,
|
|
20353
|
+
prehash: c.prehash,
|
|
20354
|
+
mapToCurve: c.mapToCurve
|
|
20355
|
+
};
|
|
20356
|
+
return { CURVE, curveOpts, hash: c.hash, eddsaOpts };
|
|
20357
|
+
}
|
|
20358
|
+
function _eddsa_new_output_to_legacy(c, eddsa2) {
|
|
20359
|
+
const Point = eddsa2.Point;
|
|
20360
|
+
const legacy = Object.assign({}, eddsa2, {
|
|
20361
|
+
ExtendedPoint: Point,
|
|
20362
|
+
CURVE: c,
|
|
20363
|
+
nBitLength: Point.Fn.BITS,
|
|
20364
|
+
nByteLength: Point.Fn.BYTES
|
|
20365
|
+
});
|
|
20366
|
+
return legacy;
|
|
20367
|
+
}
|
|
20368
|
+
function twistedEdwards(c) {
|
|
20369
|
+
const { CURVE, curveOpts, hash, eddsaOpts } = _eddsa_legacy_opts_to_new(c);
|
|
20370
|
+
const Point = edwards(CURVE, curveOpts);
|
|
20371
|
+
const EDDSA = eddsa(Point, hash, eddsaOpts);
|
|
20372
|
+
return _eddsa_new_output_to_legacy(c, EDDSA);
|
|
20373
|
+
}
|
|
20374
|
+
|
|
20375
|
+
// ../mmt-manifest-verify/node_modules/@noble/curves/esm/ed25519.js
|
|
20376
|
+
var _0n5 = /* @__PURE__ */ BigInt(0);
|
|
20377
|
+
var _1n5 = BigInt(1);
|
|
20378
|
+
var _2n3 = BigInt(2);
|
|
20379
|
+
var _3n2 = BigInt(3);
|
|
20380
|
+
var _5n2 = BigInt(5);
|
|
20381
|
+
var _8n3 = BigInt(8);
|
|
20382
|
+
var ed25519_CURVE_p = BigInt("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed");
|
|
20383
|
+
var ed25519_CURVE = /* @__PURE__ */ (() => ({
|
|
20384
|
+
p: ed25519_CURVE_p,
|
|
20385
|
+
n: BigInt("0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed"),
|
|
20386
|
+
h: _8n3,
|
|
20387
|
+
a: BigInt("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec"),
|
|
20388
|
+
d: BigInt("0x52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3"),
|
|
20389
|
+
Gx: BigInt("0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51a"),
|
|
20390
|
+
Gy: BigInt("0x6666666666666666666666666666666666666666666666666666666666666658")
|
|
20391
|
+
}))();
|
|
20392
|
+
function ed25519_pow_2_252_3(x) {
|
|
20393
|
+
const _10n = BigInt(10), _20n = BigInt(20), _40n = BigInt(40), _80n = BigInt(80);
|
|
20394
|
+
const P = ed25519_CURVE_p;
|
|
20395
|
+
const x2 = x * x % P;
|
|
20396
|
+
const b2 = x2 * x % P;
|
|
20397
|
+
const b4 = pow2(b2, _2n3, P) * b2 % P;
|
|
20398
|
+
const b5 = pow2(b4, _1n5, P) * x % P;
|
|
20399
|
+
const b10 = pow2(b5, _5n2, P) * b5 % P;
|
|
20400
|
+
const b20 = pow2(b10, _10n, P) * b10 % P;
|
|
20401
|
+
const b40 = pow2(b20, _20n, P) * b20 % P;
|
|
20402
|
+
const b80 = pow2(b40, _40n, P) * b40 % P;
|
|
20403
|
+
const b160 = pow2(b80, _80n, P) * b80 % P;
|
|
20404
|
+
const b240 = pow2(b160, _80n, P) * b80 % P;
|
|
20405
|
+
const b250 = pow2(b240, _10n, P) * b10 % P;
|
|
20406
|
+
const pow_p_5_8 = pow2(b250, _2n3, P) * x % P;
|
|
20407
|
+
return { pow_p_5_8, b2 };
|
|
20408
|
+
}
|
|
20409
|
+
function adjustScalarBytes(bytes) {
|
|
20410
|
+
bytes[0] &= 248;
|
|
20411
|
+
bytes[31] &= 127;
|
|
20412
|
+
bytes[31] |= 64;
|
|
20413
|
+
return bytes;
|
|
20414
|
+
}
|
|
20415
|
+
var ED25519_SQRT_M1 = /* @__PURE__ */ BigInt("19681161376707505956807079304988542015446066515923890162744021073123829784752");
|
|
20416
|
+
function uvRatio(u, v) {
|
|
20417
|
+
const P = ed25519_CURVE_p;
|
|
20418
|
+
const v3 = mod(v * v * v, P);
|
|
20419
|
+
const v7 = mod(v3 * v3 * v, P);
|
|
20420
|
+
const pow = ed25519_pow_2_252_3(u * v7).pow_p_5_8;
|
|
20421
|
+
let x = mod(u * v3 * pow, P);
|
|
20422
|
+
const vx2 = mod(v * x * x, P);
|
|
20423
|
+
const root1 = x;
|
|
20424
|
+
const root2 = mod(x * ED25519_SQRT_M1, P);
|
|
20425
|
+
const useRoot1 = vx2 === u;
|
|
20426
|
+
const useRoot2 = vx2 === mod(-u, P);
|
|
20427
|
+
const noRoot = vx2 === mod(-u * ED25519_SQRT_M1, P);
|
|
20428
|
+
if (useRoot1)
|
|
20429
|
+
x = root1;
|
|
20430
|
+
if (useRoot2 || noRoot)
|
|
20431
|
+
x = root2;
|
|
20432
|
+
if (isNegativeLE(x, P))
|
|
20433
|
+
x = mod(-x, P);
|
|
20434
|
+
return { isValid: useRoot1 || useRoot2, value: x };
|
|
20435
|
+
}
|
|
20436
|
+
var Fp = /* @__PURE__ */ (() => Field(ed25519_CURVE.p, { isLE: true }))();
|
|
20437
|
+
var Fn = /* @__PURE__ */ (() => Field(ed25519_CURVE.n, { isLE: true }))();
|
|
20438
|
+
var ed25519Defaults = /* @__PURE__ */ (() => ({
|
|
20439
|
+
...ed25519_CURVE,
|
|
20440
|
+
Fp,
|
|
20441
|
+
hash: sha512,
|
|
20442
|
+
adjustScalarBytes,
|
|
20443
|
+
// dom2
|
|
20444
|
+
// Ratio of u to v. Allows us to combine inversion and square root. Uses algo from RFC8032 5.1.3.
|
|
20445
|
+
// Constant-time, u/√v
|
|
20446
|
+
uvRatio
|
|
20447
|
+
}))();
|
|
20448
|
+
var ed25519 = /* @__PURE__ */ (() => twistedEdwards(ed25519Defaults))();
|
|
20449
|
+
var SQRT_M1 = ED25519_SQRT_M1;
|
|
20450
|
+
var SQRT_AD_MINUS_ONE = /* @__PURE__ */ BigInt("25063068953384623474111414158702152701244531502492656460079210482610430750235");
|
|
20451
|
+
var INVSQRT_A_MINUS_D = /* @__PURE__ */ BigInt("54469307008909316920995813868745141605393597292927456921205312896311721017578");
|
|
20452
|
+
var ONE_MINUS_D_SQ = /* @__PURE__ */ BigInt("1159843021668779879193775521855586647937357759715417654439879720876111806838");
|
|
20453
|
+
var D_MINUS_ONE_SQ = /* @__PURE__ */ BigInt("40440834346308536858101042469323190826248399146238708352240133220865137265952");
|
|
20454
|
+
var invertSqrt = (number) => uvRatio(_1n5, number);
|
|
20455
|
+
var MAX_255B = /* @__PURE__ */ BigInt("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
|
|
20456
|
+
var bytes255ToNumberLE = (bytes) => ed25519.Point.Fp.create(bytesToNumberLE(bytes) & MAX_255B);
|
|
20457
|
+
function calcElligatorRistrettoMap(r0) {
|
|
20458
|
+
const { d } = ed25519_CURVE;
|
|
20459
|
+
const P = ed25519_CURVE_p;
|
|
20460
|
+
const mod2 = (n) => Fp.create(n);
|
|
20461
|
+
const r = mod2(SQRT_M1 * r0 * r0);
|
|
20462
|
+
const Ns = mod2((r + _1n5) * ONE_MINUS_D_SQ);
|
|
20463
|
+
let c = BigInt(-1);
|
|
20464
|
+
const D = mod2((c - d * r) * mod2(r + d));
|
|
20465
|
+
let { isValid: Ns_D_is_sq, value: s } = uvRatio(Ns, D);
|
|
20466
|
+
let s_ = mod2(s * r0);
|
|
20467
|
+
if (!isNegativeLE(s_, P))
|
|
20468
|
+
s_ = mod2(-s_);
|
|
20469
|
+
if (!Ns_D_is_sq)
|
|
20470
|
+
s = s_;
|
|
20471
|
+
if (!Ns_D_is_sq)
|
|
20472
|
+
c = r;
|
|
20473
|
+
const Nt = mod2(c * (r - _1n5) * D_MINUS_ONE_SQ - D);
|
|
20474
|
+
const s2 = s * s;
|
|
20475
|
+
const W0 = mod2((s + s) * D);
|
|
20476
|
+
const W1 = mod2(Nt * SQRT_AD_MINUS_ONE);
|
|
20477
|
+
const W2 = mod2(_1n5 - s2);
|
|
20478
|
+
const W3 = mod2(_1n5 + s2);
|
|
20479
|
+
return new ed25519.Point(mod2(W0 * W3), mod2(W2 * W1), mod2(W1 * W3), mod2(W0 * W2));
|
|
20480
|
+
}
|
|
20481
|
+
function ristretto255_map(bytes) {
|
|
20482
|
+
abytes(bytes, 64);
|
|
20483
|
+
const r1 = bytes255ToNumberLE(bytes.subarray(0, 32));
|
|
20484
|
+
const R1 = calcElligatorRistrettoMap(r1);
|
|
20485
|
+
const r2 = bytes255ToNumberLE(bytes.subarray(32, 64));
|
|
20486
|
+
const R2 = calcElligatorRistrettoMap(r2);
|
|
20487
|
+
return new _RistrettoPoint(R1.add(R2));
|
|
20488
|
+
}
|
|
20489
|
+
var _RistrettoPoint = class __RistrettoPoint extends PrimeEdwardsPoint {
|
|
20490
|
+
constructor(ep) {
|
|
20491
|
+
super(ep);
|
|
20492
|
+
}
|
|
20493
|
+
static fromAffine(ap) {
|
|
20494
|
+
return new __RistrettoPoint(ed25519.Point.fromAffine(ap));
|
|
20495
|
+
}
|
|
20496
|
+
assertSame(other) {
|
|
20497
|
+
if (!(other instanceof __RistrettoPoint))
|
|
20498
|
+
throw new Error("RistrettoPoint expected");
|
|
20499
|
+
}
|
|
20500
|
+
init(ep) {
|
|
20501
|
+
return new __RistrettoPoint(ep);
|
|
20502
|
+
}
|
|
20503
|
+
/** @deprecated use `import { ristretto255_hasher } from '@noble/curves/ed25519.js';` */
|
|
20504
|
+
static hashToCurve(hex2) {
|
|
20505
|
+
return ristretto255_map(ensureBytes("ristrettoHash", hex2, 64));
|
|
20506
|
+
}
|
|
20507
|
+
static fromBytes(bytes) {
|
|
20508
|
+
abytes(bytes, 32);
|
|
20509
|
+
const { a, d } = ed25519_CURVE;
|
|
20510
|
+
const P = ed25519_CURVE_p;
|
|
20511
|
+
const mod2 = (n) => Fp.create(n);
|
|
20512
|
+
const s = bytes255ToNumberLE(bytes);
|
|
20513
|
+
if (!equalBytes2(Fp.toBytes(s), bytes) || isNegativeLE(s, P))
|
|
20514
|
+
throw new Error("invalid ristretto255 encoding 1");
|
|
20515
|
+
const s2 = mod2(s * s);
|
|
20516
|
+
const u1 = mod2(_1n5 + a * s2);
|
|
20517
|
+
const u2 = mod2(_1n5 - a * s2);
|
|
20518
|
+
const u1_2 = mod2(u1 * u1);
|
|
20519
|
+
const u2_2 = mod2(u2 * u2);
|
|
20520
|
+
const v = mod2(a * d * u1_2 - u2_2);
|
|
20521
|
+
const { isValid, value: I } = invertSqrt(mod2(v * u2_2));
|
|
20522
|
+
const Dx = mod2(I * u2);
|
|
20523
|
+
const Dy = mod2(I * Dx * v);
|
|
20524
|
+
let x = mod2((s + s) * Dx);
|
|
20525
|
+
if (isNegativeLE(x, P))
|
|
20526
|
+
x = mod2(-x);
|
|
20527
|
+
const y = mod2(u1 * Dy);
|
|
20528
|
+
const t = mod2(x * y);
|
|
20529
|
+
if (!isValid || isNegativeLE(t, P) || y === _0n5)
|
|
20530
|
+
throw new Error("invalid ristretto255 encoding 2");
|
|
20531
|
+
return new __RistrettoPoint(new ed25519.Point(x, y, _1n5, t));
|
|
20532
|
+
}
|
|
20533
|
+
/**
|
|
20534
|
+
* Converts ristretto-encoded string to ristretto point.
|
|
20535
|
+
* Described in [RFC9496](https://www.rfc-editor.org/rfc/rfc9496#name-decode).
|
|
20536
|
+
* @param hex Ristretto-encoded 32 bytes. Not every 32-byte string is valid ristretto encoding
|
|
20537
|
+
*/
|
|
20538
|
+
static fromHex(hex2) {
|
|
20539
|
+
return __RistrettoPoint.fromBytes(ensureBytes("ristrettoHex", hex2, 32));
|
|
20540
|
+
}
|
|
20541
|
+
static msm(points, scalars) {
|
|
20542
|
+
return pippenger(__RistrettoPoint, ed25519.Point.Fn, points, scalars);
|
|
20543
|
+
}
|
|
20544
|
+
/**
|
|
20545
|
+
* Encodes ristretto point to Uint8Array.
|
|
20546
|
+
* Described in [RFC9496](https://www.rfc-editor.org/rfc/rfc9496#name-encode).
|
|
20547
|
+
*/
|
|
20548
|
+
toBytes() {
|
|
20549
|
+
let { X, Y, Z, T } = this.ep;
|
|
20550
|
+
const P = ed25519_CURVE_p;
|
|
20551
|
+
const mod2 = (n) => Fp.create(n);
|
|
20552
|
+
const u1 = mod2(mod2(Z + Y) * mod2(Z - Y));
|
|
20553
|
+
const u2 = mod2(X * Y);
|
|
20554
|
+
const u2sq = mod2(u2 * u2);
|
|
20555
|
+
const { value: invsqrt } = invertSqrt(mod2(u1 * u2sq));
|
|
20556
|
+
const D1 = mod2(invsqrt * u1);
|
|
20557
|
+
const D2 = mod2(invsqrt * u2);
|
|
20558
|
+
const zInv = mod2(D1 * D2 * T);
|
|
20559
|
+
let D;
|
|
20560
|
+
if (isNegativeLE(T * zInv, P)) {
|
|
20561
|
+
let _x = mod2(Y * SQRT_M1);
|
|
20562
|
+
let _y = mod2(X * SQRT_M1);
|
|
20563
|
+
X = _x;
|
|
20564
|
+
Y = _y;
|
|
20565
|
+
D = mod2(D1 * INVSQRT_A_MINUS_D);
|
|
20566
|
+
} else {
|
|
20567
|
+
D = D2;
|
|
20568
|
+
}
|
|
20569
|
+
if (isNegativeLE(X * zInv, P))
|
|
20570
|
+
Y = mod2(-Y);
|
|
20571
|
+
let s = mod2((Z - Y) * D);
|
|
20572
|
+
if (isNegativeLE(s, P))
|
|
20573
|
+
s = mod2(-s);
|
|
20574
|
+
return Fp.toBytes(s);
|
|
20575
|
+
}
|
|
20576
|
+
/**
|
|
20577
|
+
* Compares two Ristretto points.
|
|
20578
|
+
* Described in [RFC9496](https://www.rfc-editor.org/rfc/rfc9496#name-equals).
|
|
20579
|
+
*/
|
|
20580
|
+
equals(other) {
|
|
20581
|
+
this.assertSame(other);
|
|
20582
|
+
const { X: X1, Y: Y1 } = this.ep;
|
|
20583
|
+
const { X: X2, Y: Y2 } = other.ep;
|
|
20584
|
+
const mod2 = (n) => Fp.create(n);
|
|
20585
|
+
const one = mod2(X1 * Y2) === mod2(Y1 * X2);
|
|
20586
|
+
const two = mod2(Y1 * Y2) === mod2(X1 * X2);
|
|
20587
|
+
return one || two;
|
|
20588
|
+
}
|
|
20589
|
+
is0() {
|
|
20590
|
+
return this.equals(__RistrettoPoint.ZERO);
|
|
20591
|
+
}
|
|
20592
|
+
};
|
|
20593
|
+
_RistrettoPoint.BASE = /* @__PURE__ */ (() => new _RistrettoPoint(ed25519.Point.BASE))();
|
|
20594
|
+
_RistrettoPoint.ZERO = /* @__PURE__ */ (() => new _RistrettoPoint(ed25519.Point.ZERO))();
|
|
20595
|
+
_RistrettoPoint.Fp = /* @__PURE__ */ (() => Fp)();
|
|
20596
|
+
_RistrettoPoint.Fn = /* @__PURE__ */ (() => Fn)();
|
|
20597
|
+
|
|
20598
|
+
// ../mmt-manifest-verify/dist/bc-provenance-v2-receiver.js
|
|
20599
|
+
var BCPV2_LEAD_BOUND_MS = 5e3;
|
|
20600
|
+
var REPLAY_WINDOW = 64n;
|
|
20601
|
+
var BUCKET_DEPTH = 64;
|
|
20602
|
+
var BUCKET_RATE_PER_MS = 1;
|
|
20603
|
+
var hex = (bytes) => Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
20604
|
+
function sameBytes(a, b) {
|
|
20605
|
+
if (a.byteLength !== b.byteLength)
|
|
20606
|
+
return false;
|
|
20607
|
+
let diff = 0;
|
|
20608
|
+
for (let i = 0; i < a.byteLength; i++)
|
|
20609
|
+
diff |= a[i] ^ b[i];
|
|
20610
|
+
return diff === 0;
|
|
20611
|
+
}
|
|
20612
|
+
var BcProvenanceV2Receiver = class {
|
|
20613
|
+
maxAuthDelayMs;
|
|
20614
|
+
manifestTimerMs;
|
|
20615
|
+
widenMs;
|
|
20616
|
+
/** Datagrams that left a hold queue at expiry, per path: the loss signal for failover. */
|
|
20617
|
+
expired = { multicast: 0, unicast: 0 };
|
|
20618
|
+
/**
|
|
20619
|
+
* Unicast repair (Section 7.2.7 step 2): on a receiver that can request it, how
|
|
20620
|
+
* long ago (ms) the first packet admitted into the oldest open block with an FEC
|
|
20621
|
+
* deadline was admitted; undefined with none open. A pending entry still live
|
|
20622
|
+
* then is kept instead of expiring, and is released with the block, under the
|
|
20623
|
+
* open-block bound of [MOQ-FEC] Section 9.
|
|
20624
|
+
*/
|
|
20625
|
+
keepPendingAgeMs = null;
|
|
20626
|
+
cfg;
|
|
20627
|
+
now;
|
|
20628
|
+
verifySig;
|
|
20629
|
+
accepted;
|
|
20630
|
+
queueLimit;
|
|
20631
|
+
current = null;
|
|
20632
|
+
previous = null;
|
|
20633
|
+
graceStart = {};
|
|
20634
|
+
hashAlg = null;
|
|
20635
|
+
verified = /* @__PURE__ */ new Map();
|
|
20636
|
+
highest = { multicast: /* @__PURE__ */ new Map(), mirror: /* @__PURE__ */ new Map() };
|
|
20637
|
+
closedAt = /* @__PURE__ */ new Map();
|
|
20638
|
+
lead = null;
|
|
20639
|
+
pending = /* @__PURE__ */ new Map();
|
|
20640
|
+
admittedUntil = /* @__PURE__ */ new Map();
|
|
20641
|
+
hold = { multicast: [], unicast: [] };
|
|
20642
|
+
holdBytes = { multicast: 0, unicast: 0 };
|
|
20643
|
+
order = 0;
|
|
20644
|
+
buckets;
|
|
20645
|
+
constructor(cfg) {
|
|
20646
|
+
this.maxAuthDelayMs = cfg.maxAuthDelayMs ?? 200;
|
|
20647
|
+
this.manifestTimerMs = cfg.manifestTimerMs ?? 20;
|
|
20648
|
+
if (!(this.maxAuthDelayMs >= 50 && this.maxAuthDelayMs <= 1e3) || !(this.manifestTimerMs >= 1)) {
|
|
20649
|
+
throw new Error("BCPV2 endpoint ignored: maxAuthDelayMs or manifestTimerMs out of range");
|
|
20650
|
+
}
|
|
20651
|
+
if (!(cfg.bandwidth > 0))
|
|
20652
|
+
throw new Error("BCPV2 endpoint ignored: protected endpoint without bandwidth");
|
|
20653
|
+
const kids = new Set(cfg.keys.map((k) => k.kid));
|
|
20654
|
+
const firsts = new Set(cfg.keys.map((k) => k.firstEpoch));
|
|
20655
|
+
if (cfg.keys.length < 1 || kids.size !== cfg.keys.length || firsts.size !== cfg.keys.length) {
|
|
20656
|
+
throw new Error("BCPV2 key set invalid: no keys, or a repeated kid or firstEpoch");
|
|
20657
|
+
}
|
|
20658
|
+
this.cfg = cfg;
|
|
20659
|
+
this.widenMs = Math.min(Math.max(cfg.widenMs ?? 0, 0), BCPV2_LEAD_BOUND_MS);
|
|
20660
|
+
this.now = cfg.now ?? Date.now;
|
|
20661
|
+
this.verifySig = cfg.verifySignature ?? ((pk, msg, sig) => {
|
|
20662
|
+
try {
|
|
20663
|
+
return ed25519.verify(sig, msg, pk, { zip215: false });
|
|
20664
|
+
} catch {
|
|
20665
|
+
return false;
|
|
20666
|
+
}
|
|
20667
|
+
});
|
|
20668
|
+
this.accepted = cfg.acceptedHashAlgs ?? [BCPV2_HASH_ALG_SHA256];
|
|
20669
|
+
const bandwidth = Math.min(cfg.bandwidth, 1e10);
|
|
20670
|
+
this.queueLimit = {
|
|
20671
|
+
multicast: this.maxAuthDelayMs * bandwidth / 8e3,
|
|
20672
|
+
unicast: (this.maxAuthDelayMs + this.widenMs) * bandwidth / 8e3
|
|
20673
|
+
};
|
|
20674
|
+
const t = this.now();
|
|
20675
|
+
this.buckets = {
|
|
20676
|
+
multicast: { tokens: BUCKET_DEPTH, last: t, dropped: 0 },
|
|
20677
|
+
mirror: { tokens: BUCKET_DEPTH, last: t, dropped: 0 }
|
|
20678
|
+
};
|
|
20679
|
+
}
|
|
20680
|
+
get grace() {
|
|
20681
|
+
return this.maxAuthDelayMs + 2 * this.manifestTimerMs;
|
|
20682
|
+
}
|
|
20683
|
+
/** The mirror's lead in force: 0 without a manifest track, the bound until one is measured. */
|
|
20684
|
+
get leadMs() {
|
|
20685
|
+
if (!this.cfg.applyLead)
|
|
20686
|
+
return 0;
|
|
20687
|
+
return this.lead ?? BCPV2_LEAD_BOUND_MS;
|
|
20688
|
+
}
|
|
20689
|
+
/** Step 1: a record datagram on `manifestPort`, or a record object of the manifest track. */
|
|
20690
|
+
onRecord(bytes, path) {
|
|
20691
|
+
const now = this.now();
|
|
20692
|
+
this.expire(now);
|
|
20693
|
+
const drop = (verdict) => ({ verdict, admitted: [] });
|
|
20694
|
+
const len = bytes.byteLength;
|
|
20695
|
+
if (len < BCPV2_MIN_RECORD_LENGTH || len > BCPV2_MAX_RECORD_LENGTH || (len - 132) % 32 !== 0)
|
|
20696
|
+
return drop("length");
|
|
20697
|
+
const view2 = new DataView(bytes.buffer, bytes.byteOffset, len);
|
|
20698
|
+
if (view2.getUint32(0) !== 1296583986)
|
|
20699
|
+
return drop("magic");
|
|
20700
|
+
const kid = view2.getUint32(4);
|
|
20701
|
+
const epoch = view2.getUint32(8);
|
|
20702
|
+
const mseq = view2.getBigUint64(12);
|
|
20703
|
+
const count = view2.getUint16(28);
|
|
20704
|
+
const hashAlg = bytes[30];
|
|
20705
|
+
const sent = view2.getUint32(32);
|
|
20706
|
+
const key = this.cfg.keys.find((k) => k.kid === kid);
|
|
20707
|
+
if (!key)
|
|
20708
|
+
return drop("kid");
|
|
20709
|
+
const next = Math.min(...this.cfg.keys.map((k) => k.firstEpoch).filter((f) => f > key.firstEpoch), Infinity);
|
|
20710
|
+
if (epoch < key.firstEpoch || epoch >= next)
|
|
20711
|
+
return drop("epoch");
|
|
20712
|
+
const id = `${kid}:${epoch}:${mseq}`;
|
|
20713
|
+
const seen = this.verified.get(id);
|
|
20714
|
+
if (seen) {
|
|
20715
|
+
const admitted2 = hex(sha2562(bytes)) === seen.digest ? this.arrival(seen, path, now) : [];
|
|
20716
|
+
return { verdict: "duplicate", admitted: admitted2 };
|
|
20717
|
+
}
|
|
20718
|
+
const ke = `${kid}:${epoch}`;
|
|
20719
|
+
if (this.current === null) {
|
|
20720
|
+
if (epoch < this.cfg.minEpoch || epoch < (this.cfg.pinnedEpochFloor?.get(kid) ?? 0))
|
|
20721
|
+
return drop("epoch");
|
|
20722
|
+
} else if (epoch < this.current.epoch && !this.previousAdmitted(epoch, path, now)) {
|
|
20723
|
+
return drop("epoch");
|
|
20724
|
+
}
|
|
20725
|
+
const floor = this.floor(ke, path);
|
|
20726
|
+
if (floor !== void 0 && mseq <= floor - REPLAY_WINDOW)
|
|
20727
|
+
return drop("mseq");
|
|
20728
|
+
const closed = this.closedAt.get(ke);
|
|
20729
|
+
if (closed !== void 0 && mseq > closed)
|
|
20730
|
+
return drop("closed");
|
|
20731
|
+
if (this.cfg.checkFreshness !== false) {
|
|
20732
|
+
const behind = 7e3 + this.maxAuthDelayMs + (path === "mirror" ? this.widenMs : 0);
|
|
20733
|
+
if (sent * 1e3 < now - behind || sent * 1e3 > now + 6e3)
|
|
20734
|
+
return drop("sent");
|
|
20735
|
+
}
|
|
20736
|
+
if (count !== (len - 132) / 32)
|
|
20737
|
+
return drop("count");
|
|
20738
|
+
if (!this.accepted.includes(hashAlg) || this.hashAlg !== null && hashAlg !== this.hashAlg)
|
|
20739
|
+
return drop("hash_alg");
|
|
20740
|
+
if (this.cfg.catalogCtx && !sameBytes(bytes.subarray(36, 68), this.cfg.catalogCtx))
|
|
20741
|
+
return drop("ctx");
|
|
20742
|
+
if (!this.take(path, now))
|
|
20743
|
+
return drop("budget");
|
|
20744
|
+
const record = decodeBcProvenanceV2Record(bytes);
|
|
20745
|
+
if (!this.verifySig(key.publicKey, bcProvenanceV2SignedMessage(record, this.cfg.endpoint), record.signature)) {
|
|
20746
|
+
return drop("signature");
|
|
20747
|
+
}
|
|
20748
|
+
if (this.current === null || epoch > this.current.epoch) {
|
|
20749
|
+
if (this.current !== null) {
|
|
20750
|
+
this.previous = { epoch: this.current.epoch, deadline: now + BCPV2_LEAD_BOUND_MS + this.grace };
|
|
20751
|
+
}
|
|
20752
|
+
this.current = { epoch, since: now };
|
|
20753
|
+
this.graceStart = { [path]: now };
|
|
20754
|
+
if (this.hashAlg === null) {
|
|
20755
|
+
this.hashAlg = hashAlg;
|
|
20756
|
+
for (const queue of Object.values(this.hold)) {
|
|
20757
|
+
for (const held of queue)
|
|
20758
|
+
held.digest ??= this.digest(held.datagram);
|
|
20759
|
+
}
|
|
20760
|
+
}
|
|
20761
|
+
} else if (epoch === this.current.epoch) {
|
|
20762
|
+
this.graceStart[path] ??= now;
|
|
20763
|
+
}
|
|
20764
|
+
const entry = {
|
|
20765
|
+
kid,
|
|
20766
|
+
epoch,
|
|
20767
|
+
mseq,
|
|
20768
|
+
path,
|
|
20769
|
+
arrival: now,
|
|
20770
|
+
sent,
|
|
20771
|
+
digest: hex(sha2562(bytes)),
|
|
20772
|
+
pairs: record.digests.map((d, i) => ({ digest: hex(d), seq: record.firstSeq + BigInt(i), admitted: false })),
|
|
20773
|
+
awaitingMulticast: path === "mirror"
|
|
20774
|
+
};
|
|
20775
|
+
this.verified.set(id, entry);
|
|
20776
|
+
this.raise(ke, path, mseq);
|
|
20777
|
+
if (record.flags & 1)
|
|
20778
|
+
this.closedAt.set(ke, mseq);
|
|
20779
|
+
const widen = path === "mirror" ? this.cfg.applyLead ? this.leadMs : this.widenMs : 0;
|
|
20780
|
+
const admitted = [];
|
|
20781
|
+
for (const pair of entry.pairs)
|
|
20782
|
+
this.handlePair(pair, now, now + this.grace + widen, now, admitted);
|
|
20783
|
+
this.dropAdmittedHeld();
|
|
20784
|
+
this.prune(now);
|
|
20785
|
+
return { verdict: "verified", admitted };
|
|
20786
|
+
}
|
|
20787
|
+
/** Step 2: a data datagram on `port` (or a packet of a listed track over MoQ unicast). */
|
|
20788
|
+
onDatagram(datagram, path) {
|
|
20789
|
+
const now = this.now();
|
|
20790
|
+
this.expire(now);
|
|
20791
|
+
if (this.hashAlg !== null) {
|
|
20792
|
+
const d = this.digest(datagram);
|
|
20793
|
+
if (this.consumePending(d, now))
|
|
20794
|
+
return { verdict: "admitted", admitted: [datagram] };
|
|
20795
|
+
if (this.admittedUntil.has(d))
|
|
20796
|
+
return { verdict: "duplicate", admitted: [] };
|
|
20797
|
+
return this.enqueue({ digest: d, datagram, expiry: 0, order: 0 }, path, now);
|
|
20798
|
+
}
|
|
20799
|
+
return this.enqueue({ datagram, expiry: 0, order: 0 }, path, now);
|
|
20800
|
+
}
|
|
20801
|
+
/**
|
|
20802
|
+
* A source packet recovered by FEC decode, its base SS_ID (SS_Start + ESI)
|
|
20803
|
+
* appended: admitted only on a pending entry it consumes, never held. One no
|
|
20804
|
+
* verified record lists, or a copy of an admitted packet, is dropped.
|
|
20805
|
+
*/
|
|
20806
|
+
admitRecovered(datagram) {
|
|
20807
|
+
const now = this.now();
|
|
20808
|
+
this.expire(now);
|
|
20809
|
+
return this.hashAlg !== null && this.consumePending(this.digest(datagram), now);
|
|
20810
|
+
}
|
|
20811
|
+
/**
|
|
20812
|
+
* Discard the per-flow state on a `streamId` change or a key-set change that fails the
|
|
20813
|
+
* flow's current (kid, epoch): budgets and hold queues survive, held digests are cleared.
|
|
20814
|
+
*/
|
|
20815
|
+
resetGeneration() {
|
|
20816
|
+
this.current = null;
|
|
20817
|
+
this.previous = null;
|
|
20818
|
+
this.graceStart = {};
|
|
20819
|
+
this.hashAlg = null;
|
|
20820
|
+
this.verified.clear();
|
|
20821
|
+
this.highest = { multicast: /* @__PURE__ */ new Map(), mirror: /* @__PURE__ */ new Map() };
|
|
20822
|
+
this.closedAt.clear();
|
|
20823
|
+
this.lead = null;
|
|
20824
|
+
this.pending.clear();
|
|
20825
|
+
this.admittedUntil.clear();
|
|
20826
|
+
for (const queue of Object.values(this.hold))
|
|
20827
|
+
for (const held of queue)
|
|
20828
|
+
held.digest = void 0;
|
|
20829
|
+
}
|
|
20830
|
+
/** The receiver ceased to hold the endpoint: drop everything; no drop here is an expiry. */
|
|
20831
|
+
release() {
|
|
20832
|
+
this.resetGeneration();
|
|
20833
|
+
this.hold = { multicast: [], unicast: [] };
|
|
20834
|
+
this.holdBytes = { multicast: 0, unicast: 0 };
|
|
20835
|
+
}
|
|
20836
|
+
digest(datagram) {
|
|
20837
|
+
return hex(bcProvenanceV2DatagramDigest(this.cfg.endpoint, datagram));
|
|
20838
|
+
}
|
|
20839
|
+
/** Consume the lowest-seq pending entry for `digest`, admitting it. */
|
|
20840
|
+
consumePending(digest, now) {
|
|
20841
|
+
const list = this.pending.get(digest);
|
|
20842
|
+
if (!list?.length)
|
|
20843
|
+
return false;
|
|
20844
|
+
list.sort((a, b) => a.pair.seq < b.pair.seq ? -1 : a.pair.seq > b.pair.seq ? 1 : 0);
|
|
20845
|
+
const hit = list.shift();
|
|
20846
|
+
if (list.length === 0)
|
|
20847
|
+
this.pending.delete(digest);
|
|
20848
|
+
hit.pair.pending = void 0;
|
|
20849
|
+
hit.pair.admitted = true;
|
|
20850
|
+
this.admittedUntil.set(digest, now + this.maxAuthDelayMs + this.leadMs);
|
|
20851
|
+
return true;
|
|
20852
|
+
}
|
|
20853
|
+
enqueue(held, path, now) {
|
|
20854
|
+
if (this.holdBytes[path] + held.datagram.byteLength > this.queueLimit[path]) {
|
|
20855
|
+
return { verdict: "overflow", admitted: [] };
|
|
20856
|
+
}
|
|
20857
|
+
held.expiry = now + this.maxAuthDelayMs + (path === "unicast" ? this.widenMs : 0);
|
|
20858
|
+
held.order = this.order++;
|
|
20859
|
+
this.hold[path].push(held);
|
|
20860
|
+
this.holdBytes[path] += held.datagram.byteLength;
|
|
20861
|
+
return { verdict: "held", admitted: [] };
|
|
20862
|
+
}
|
|
20863
|
+
/** Remove and return the oldest held datagram with `digest`, across both queues. */
|
|
20864
|
+
takeHeld(digest) {
|
|
20865
|
+
let best;
|
|
20866
|
+
for (const path of ["multicast", "unicast"]) {
|
|
20867
|
+
const index = this.hold[path].findIndex((h) => h.digest === digest);
|
|
20868
|
+
if (index >= 0 && (!best || this.hold[path][index].order < best.order)) {
|
|
20869
|
+
best = { path, index, order: this.hold[path][index].order };
|
|
20870
|
+
}
|
|
20871
|
+
}
|
|
20872
|
+
if (!best)
|
|
20873
|
+
return void 0;
|
|
20874
|
+
const [held] = this.hold[best.path].splice(best.index, 1);
|
|
20875
|
+
this.holdBytes[best.path] -= held.datagram.byteLength;
|
|
20876
|
+
return held.datagram;
|
|
20877
|
+
}
|
|
20878
|
+
dropAdmittedHeld() {
|
|
20879
|
+
for (const path of ["multicast", "unicast"]) {
|
|
20880
|
+
this.hold[path] = this.hold[path].filter((h) => {
|
|
20881
|
+
const keep = h.digest === void 0 || !this.admittedUntil.has(h.digest);
|
|
20882
|
+
if (!keep)
|
|
20883
|
+
this.holdBytes[path] -= h.datagram.byteLength;
|
|
20884
|
+
return keep;
|
|
20885
|
+
});
|
|
20886
|
+
}
|
|
20887
|
+
}
|
|
20888
|
+
/** Step 1 pair handling: admit a held copy, or insert a pending entry. */
|
|
20889
|
+
handlePair(pair, now, expiry, inserted, out) {
|
|
20890
|
+
const datagram = this.takeHeld(pair.digest);
|
|
20891
|
+
if (datagram) {
|
|
20892
|
+
pair.admitted = true;
|
|
20893
|
+
this.admittedUntil.set(pair.digest, now + this.maxAuthDelayMs + this.leadMs);
|
|
20894
|
+
out.push(datagram);
|
|
20895
|
+
return;
|
|
20896
|
+
}
|
|
20897
|
+
const entry = { pair, inserted, expiry };
|
|
20898
|
+
pair.pending = entry;
|
|
20899
|
+
const list = this.pending.get(pair.digest);
|
|
20900
|
+
if (list)
|
|
20901
|
+
list.push(entry);
|
|
20902
|
+
else
|
|
20903
|
+
this.pending.set(pair.digest, [entry]);
|
|
20904
|
+
}
|
|
20905
|
+
/** A byte-identical copy dropped by the already-verified lookup. */
|
|
20906
|
+
arrival(seen, path, now) {
|
|
20907
|
+
const ke = `${seen.kid}:${seen.epoch}`;
|
|
20908
|
+
this.raise(ke, path, seen.mseq);
|
|
20909
|
+
if (this.current && seen.epoch === this.current.epoch)
|
|
20910
|
+
this.graceStart[path] ??= now;
|
|
20911
|
+
if (path !== "multicast" || !seen.awaitingMulticast)
|
|
20912
|
+
return [];
|
|
20913
|
+
seen.awaitingMulticast = false;
|
|
20914
|
+
if (!this.cfg.applyLead)
|
|
20915
|
+
return [];
|
|
20916
|
+
const t = this.manifestTimerMs;
|
|
20917
|
+
const interval = Math.min(Math.ceil((now - seen.arrival) / t) * t, BCPV2_LEAD_BOUND_MS);
|
|
20918
|
+
if (this.lead === null) {
|
|
20919
|
+
this.lead = interval;
|
|
20920
|
+
return [];
|
|
20921
|
+
}
|
|
20922
|
+
if (interval <= this.lead)
|
|
20923
|
+
return [];
|
|
20924
|
+
this.lead = interval;
|
|
20925
|
+
const out = [];
|
|
20926
|
+
const records = [seen, ...[...this.verified.values()].filter((v) => v.awaitingMulticast && v.arrival >= now - interval)];
|
|
20927
|
+
for (const v of records) {
|
|
20928
|
+
for (const pair of v.pairs) {
|
|
20929
|
+
if (pair.admitted) {
|
|
20930
|
+
this.admittedUntil.set(pair.digest, now + this.maxAuthDelayMs + interval);
|
|
20931
|
+
} else if (pair.pending) {
|
|
20932
|
+
pair.pending.expiry = pair.pending.inserted + this.grace + interval;
|
|
20933
|
+
} else {
|
|
20934
|
+
this.handlePair(pair, now, now + this.grace + interval, now, out);
|
|
20935
|
+
}
|
|
20936
|
+
}
|
|
20937
|
+
}
|
|
20938
|
+
this.dropAdmittedHeld();
|
|
20939
|
+
return out;
|
|
20940
|
+
}
|
|
20941
|
+
previousAdmitted(epoch, path, now) {
|
|
20942
|
+
if (!this.previous || epoch !== this.previous.epoch)
|
|
20943
|
+
return false;
|
|
20944
|
+
const m = this.graceStart.multicast;
|
|
20945
|
+
if (path === "multicast")
|
|
20946
|
+
return m === void 0 || now <= m + this.grace;
|
|
20947
|
+
const starts = [m, this.graceStart.mirror].filter((s) => s !== void 0);
|
|
20948
|
+
return starts.length > 0 && now <= Math.min(...starts) + this.grace;
|
|
20949
|
+
}
|
|
20950
|
+
/** The replay floor this record meets: its path's, or the higher of both from the mirror. */
|
|
20951
|
+
floor(ke, path) {
|
|
20952
|
+
const m = this.highest.multicast.get(ke);
|
|
20953
|
+
if (path === "multicast")
|
|
20954
|
+
return m;
|
|
20955
|
+
const r = this.highest.mirror.get(ke);
|
|
20956
|
+
if (m === void 0)
|
|
20957
|
+
return r;
|
|
20958
|
+
return r === void 0 || m > r ? m : r;
|
|
20959
|
+
}
|
|
20960
|
+
raise(ke, path, mseq) {
|
|
20961
|
+
const h = this.highest[path].get(ke);
|
|
20962
|
+
if (h === void 0 || mseq > h)
|
|
20963
|
+
this.highest[path].set(ke, mseq);
|
|
20964
|
+
}
|
|
20965
|
+
take(path, now) {
|
|
20966
|
+
const b = this.buckets[path];
|
|
20967
|
+
b.tokens = Math.min(BUCKET_DEPTH, b.tokens + (now - b.last) * BUCKET_RATE_PER_MS);
|
|
20968
|
+
b.last = now;
|
|
20969
|
+
if (b.tokens < 1) {
|
|
20970
|
+
b.dropped++;
|
|
20971
|
+
return false;
|
|
20972
|
+
}
|
|
20973
|
+
b.tokens -= 1;
|
|
20974
|
+
return true;
|
|
20975
|
+
}
|
|
20976
|
+
expire(now) {
|
|
20977
|
+
for (const path of ["multicast", "unicast"]) {
|
|
20978
|
+
this.hold[path] = this.hold[path].filter((h) => {
|
|
20979
|
+
if (h.expiry > now)
|
|
20980
|
+
return true;
|
|
20981
|
+
this.holdBytes[path] -= h.datagram.byteLength;
|
|
20982
|
+
this.expired[path]++;
|
|
20983
|
+
return false;
|
|
20984
|
+
});
|
|
20985
|
+
}
|
|
20986
|
+
let keepFrom = null;
|
|
20987
|
+
for (const [d, list] of this.pending) {
|
|
20988
|
+
const live = list.filter((p) => {
|
|
20989
|
+
if (p.expiry > now)
|
|
20990
|
+
return true;
|
|
20991
|
+
if (keepFrom === null) {
|
|
20992
|
+
const age = this.keepPendingAgeMs?.();
|
|
20993
|
+
keepFrom = age === void 0 ? void 0 : now - age;
|
|
20994
|
+
}
|
|
20995
|
+
if (keepFrom !== void 0 && keepFrom !== null && p.expiry > keepFrom)
|
|
20996
|
+
return true;
|
|
20997
|
+
p.pair.pending = void 0;
|
|
20998
|
+
return false;
|
|
20999
|
+
});
|
|
21000
|
+
if (live.length)
|
|
21001
|
+
this.pending.set(d, live);
|
|
21002
|
+
else
|
|
21003
|
+
this.pending.delete(d);
|
|
21004
|
+
}
|
|
21005
|
+
for (const [d, until] of this.admittedUntil)
|
|
21006
|
+
if (until <= now)
|
|
21007
|
+
this.admittedUntil.delete(d);
|
|
21008
|
+
if (this.previous && now > this.previous.deadline)
|
|
21009
|
+
this.previous = null;
|
|
21010
|
+
}
|
|
21011
|
+
/**
|
|
21012
|
+
* Retire verified entries below their arrival path's replay floor, keeping a mirrored entry
|
|
21013
|
+
* while the freshness check would still pass its copy on `manifestPort`.
|
|
21014
|
+
* ponytail: linear scan per record; index by (kid, epoch) if retained sets grow past a few thousand.
|
|
21015
|
+
*/
|
|
21016
|
+
prune(now) {
|
|
21017
|
+
const retention = 7e3 + this.maxAuthDelayMs;
|
|
21018
|
+
for (const [id, v] of this.verified) {
|
|
21019
|
+
const h = this.highest[v.path].get(`${v.kid}:${v.epoch}`);
|
|
21020
|
+
const aboveFloor = h === void 0 || v.mseq > h - REPLAY_WINDOW;
|
|
21021
|
+
const fresh = this.cfg.checkFreshness === false ? v.arrival + retention >= now : v.sent * 1e3 + retention >= now;
|
|
21022
|
+
const current = this.current && (v.epoch === this.current.epoch || v.epoch === this.previous?.epoch);
|
|
21023
|
+
if (current && (aboveFloor || v.path === "mirror" && fresh))
|
|
21024
|
+
continue;
|
|
21025
|
+
this.verified.delete(id);
|
|
21026
|
+
}
|
|
21027
|
+
}
|
|
21028
|
+
};
|
|
21029
|
+
|
|
17859
21030
|
// src/multicast-manager.ts
|
|
17860
21031
|
import { deriveFecInterleaveDepth as deriveFecInterleaveDepth3, FecBlockMapper as FecBlockMapper2 } from "@blockcast/mmt-fec";
|
|
17861
21032
|
|
|
@@ -17984,11 +21155,12 @@ function catalogFecUnavailable(fec, fecPath) {
|
|
|
17984
21155
|
}
|
|
17985
21156
|
|
|
17986
21157
|
// src/multicast-manager.ts
|
|
17987
|
-
function freezeIngress(tracks) {
|
|
21158
|
+
function freezeIngress(tracks, flow) {
|
|
17988
21159
|
return Object.freeze({
|
|
17989
21160
|
tracks: Object.freeze(
|
|
17990
21161
|
[...tracks].map(({ name, packetId }) => Object.freeze({ name, packetId }))
|
|
17991
|
-
)
|
|
21162
|
+
),
|
|
21163
|
+
...flow === void 0 ? {} : { flow }
|
|
17992
21164
|
});
|
|
17993
21165
|
}
|
|
17994
21166
|
function sameIdSet(a, b) {
|
|
@@ -18018,6 +21190,9 @@ function newBlockPolicyStats(packetId, catalogTrackName, epoch) {
|
|
|
18018
21190
|
repairsSkippedRttExceedsBudget: 0,
|
|
18019
21191
|
repairsSkippedPresentationDelayUnavailable: 0,
|
|
18020
21192
|
repairsSkippedRangeNoAnchor: 0,
|
|
21193
|
+
repairsSkippedGroupCannotComplete: 0,
|
|
21194
|
+
repairsSkippedMootRap: 0,
|
|
21195
|
+
repairStops: { preempted: 0, reprioritized: 0, expired: 0, resolved: 0, reset: 0, disposed: 0, moot: 0 },
|
|
18021
21196
|
lastRepairSkip: null,
|
|
18022
21197
|
keyframeRepairFetchesSent: 0,
|
|
18023
21198
|
rangeRepairFetchesSent: 0,
|
|
@@ -18102,7 +21277,19 @@ var REPAIR_PRIORITY_REASONS = /* @__PURE__ */ new Set([
|
|
|
18102
21277
|
var REPAIR_SKIP_REASONS = /* @__PURE__ */ new Set([
|
|
18103
21278
|
"rtt-unmeasured",
|
|
18104
21279
|
"rtt-exceeds-budget",
|
|
18105
|
-
"presentation-delay-unavailable"
|
|
21280
|
+
"presentation-delay-unavailable",
|
|
21281
|
+
"range-no-anchor",
|
|
21282
|
+
"group-cannot-complete",
|
|
21283
|
+
"moot-rap"
|
|
21284
|
+
]);
|
|
21285
|
+
var REPAIR_STOP_REASONS = /* @__PURE__ */ new Set([
|
|
21286
|
+
"preempted",
|
|
21287
|
+
"reprioritized",
|
|
21288
|
+
"expired",
|
|
21289
|
+
"resolved",
|
|
21290
|
+
"reset",
|
|
21291
|
+
"disposed",
|
|
21292
|
+
"moot"
|
|
18106
21293
|
]);
|
|
18107
21294
|
var REPAIR_DEADLINE_BOUNDS = /* @__PURE__ */ new Set(["wait", "presentation", "keyframe", "retention"]);
|
|
18108
21295
|
var GAP_REASONS = {
|
|
@@ -18161,6 +21348,8 @@ function isRepairLifecycleEvent(value) {
|
|
|
18161
21348
|
]) && isRepairBlockIdentity(block) && (value["priority"] === 0 || value["priority"] === 1 || value["priority"] === 2) && isFiniteNumber(value["rttMs"]) && value["rttMs"] > 0 && isFiniteNumber(value["budgetMs"]) && value["budgetMs"] > value["rttMs"] && isNullableNonNegative(value["delayMs"]) && isNullableNonNegative(value["blockAgeMs"]) && value["delayMs"] === null === (value["blockAgeMs"] === null) && REPAIR_DEADLINE_BOUNDS.has(value["bound"]);
|
|
18162
21349
|
case "repair-skipped":
|
|
18163
21350
|
return hasExactKeys(value, ["version", "type", "block", "reason", "rttMs", "budgetMs"]) && isRepairBlockIdentity(block) && REPAIR_SKIP_REASONS.has(value["reason"]) && isNullableNonNegative(value["rttMs"]) && isFiniteNumber(value["budgetMs"]);
|
|
21351
|
+
case "repair-stopped":
|
|
21352
|
+
return hasExactKeys(value, ["version", "type", "block", "reason"]) && isRepairBlockIdentity(block) && REPAIR_STOP_REASONS.has(value["reason"]);
|
|
18164
21353
|
case "repair-priority": {
|
|
18165
21354
|
const blockClass = value["blockClass"];
|
|
18166
21355
|
const action = value["action"];
|
|
@@ -18320,7 +21509,7 @@ function altGroupKey(track) {
|
|
|
18320
21509
|
}
|
|
18321
21510
|
function repairTrackNamesBySource(tracks) {
|
|
18322
21511
|
const bySource = /* @__PURE__ */ new Map();
|
|
18323
|
-
const
|
|
21512
|
+
const add2 = (source, repair) => {
|
|
18324
21513
|
if (!source || !repair) return;
|
|
18325
21514
|
const names = bySource.get(source) ?? /* @__PURE__ */ new Set();
|
|
18326
21515
|
names.add(repair);
|
|
@@ -18328,9 +21517,9 @@ function repairTrackNamesBySource(tracks) {
|
|
|
18328
21517
|
};
|
|
18329
21518
|
for (const track of tracks) {
|
|
18330
21519
|
const repairTrack = typeof track.fec === "object" && track.fec !== null ? track.fec.repairTrack : void 0;
|
|
18331
|
-
if (typeof repairTrack === "string")
|
|
21520
|
+
if (typeof repairTrack === "string") add2(track.name, repairTrack);
|
|
18332
21521
|
if (track.packaging === "fec-repair" && Array.isArray(track.depends) && track.depends.length === 1) {
|
|
18333
|
-
|
|
21522
|
+
add2(track.depends[0], track.name);
|
|
18334
21523
|
}
|
|
18335
21524
|
}
|
|
18336
21525
|
return bySource;
|
|
@@ -18340,6 +21529,10 @@ var MulticastManager = class {
|
|
|
18340
21529
|
#router;
|
|
18341
21530
|
#hostMfuTimeoutMs;
|
|
18342
21531
|
#transport;
|
|
21532
|
+
/** The protected flow of the current join and its manifestPort subscription. */
|
|
21533
|
+
#provenanceJoin = null;
|
|
21534
|
+
#onFailback = null;
|
|
21535
|
+
#failbackHooked = false;
|
|
18343
21536
|
/** Live joinRepairEndpoints() releases → the repair owners they registered,
|
|
18344
21537
|
* re-applied after configureMultiTrack() clears catalog-derived owners. */
|
|
18345
21538
|
#repairReleases = /* @__PURE__ */ new Map();
|
|
@@ -18392,10 +21585,6 @@ var MulticastManager = class {
|
|
|
18392
21585
|
reassemblyPolicy: options?.reassemblyPolicy,
|
|
18393
21586
|
reassembler: options?.reassembler,
|
|
18394
21587
|
maxBufferedMfus: options?.maxBufferedMfus,
|
|
18395
|
-
manifestVerifier: options?.manifestVerifier,
|
|
18396
|
-
manifestVerifierOwnership: options?.manifestVerifierOwnership,
|
|
18397
|
-
resolveManifestIdentity: options?.resolveManifestIdentity,
|
|
18398
|
-
resolveManifestIdentityForRoute: options?.resolveManifestIdentityForRoute,
|
|
18399
21588
|
// The router has reset its own state for the stream; reset the rest.
|
|
18400
21589
|
onPublisherRestart: (packetId, catalogTrackName) => {
|
|
18401
21590
|
this.#resetStreamOutsideRouter(packetId, catalogTrackName);
|
|
@@ -18442,7 +21631,7 @@ var MulticastManager = class {
|
|
|
18442
21631
|
});
|
|
18443
21632
|
this.#hostMfuTimeoutMs = this.#router.reassembler.timeoutMs;
|
|
18444
21633
|
this.#transport = new MulticastTransport((packet, ingress) => {
|
|
18445
|
-
this.#router.feedPacket(packet, "multicast",
|
|
21634
|
+
this.#router.feedPacket(packet, "multicast", ingress);
|
|
18446
21635
|
});
|
|
18447
21636
|
this.#transport.onDelivery((delivery) => {
|
|
18448
21637
|
this.#fec.setMulticastSymbolDelivery(delivery);
|
|
@@ -18450,17 +21639,7 @@ var MulticastManager = class {
|
|
|
18450
21639
|
this.#fec.onBlockRecovered = (sbn, packetId, data, meta, recoveredTrackName) => {
|
|
18451
21640
|
const recovered = new Uint8Array(data);
|
|
18452
21641
|
const catalogTrackName = recoveredTrackName ?? this.#fec.catalogTrackNameForBlock(sbn, packetId);
|
|
18453
|
-
|
|
18454
|
-
this.#router.routeRecoveredSymbol(sbn, recovered, packetId, meta.esi);
|
|
18455
|
-
} else {
|
|
18456
|
-
this.#router.routeRecoveredSymbol(
|
|
18457
|
-
sbn,
|
|
18458
|
-
recovered,
|
|
18459
|
-
packetId,
|
|
18460
|
-
meta.esi,
|
|
18461
|
-
catalogTrackName
|
|
18462
|
-
);
|
|
18463
|
-
}
|
|
21642
|
+
return catalogTrackName === void 0 ? this.#router.routeRecoveredSymbol(sbn, recovered, packetId, meta.esi) : this.#router.routeRecoveredSymbol(sbn, recovered, packetId, meta.esi, catalogTrackName);
|
|
18464
21643
|
};
|
|
18465
21644
|
this.#fec.onRepairResolved = (sbn, packetId, _outcome, catalogTrackName) => {
|
|
18466
21645
|
this.clearRepairForBlock(sbn, packetId, catalogTrackName);
|
|
@@ -18535,6 +21714,10 @@ var MulticastManager = class {
|
|
|
18535
21714
|
get router() {
|
|
18536
21715
|
return this.#router;
|
|
18537
21716
|
}
|
|
21717
|
+
/** Repair FETCH wiring (wireRepairFetch): this manager can request unicast repair. */
|
|
21718
|
+
setUnicastRepair(enabled) {
|
|
21719
|
+
this.#router.setUnicastRepair(enabled);
|
|
21720
|
+
}
|
|
18538
21721
|
get transport() {
|
|
18539
21722
|
return this.#transport;
|
|
18540
21723
|
}
|
|
@@ -18945,8 +22128,35 @@ var MulticastManager = class {
|
|
|
18945
22128
|
onTakeover(cb) {
|
|
18946
22129
|
this.#transport.onTakeover(cb);
|
|
18947
22130
|
}
|
|
22131
|
+
/** Failback un-protects the join's flow (7.2.7 failover), so the fallback's MoQ packets are read. */
|
|
18948
22132
|
onFailback(cb) {
|
|
18949
|
-
this.#
|
|
22133
|
+
this.#onFailback = cb;
|
|
22134
|
+
this.#hookFailback();
|
|
22135
|
+
}
|
|
22136
|
+
/**
|
|
22137
|
+
* Install the manager's own failback listener on the media transport, which
|
|
22138
|
+
* arms its silence watchdog: a protected join un-protects on failover even
|
|
22139
|
+
* when no player listens, else its tracks' MoQ copies stay dropped.
|
|
22140
|
+
*/
|
|
22141
|
+
#hookFailback() {
|
|
22142
|
+
if (this.#failbackHooked) return;
|
|
22143
|
+
this.#failbackHooked = true;
|
|
22144
|
+
this.#transport.onFailback((reason) => {
|
|
22145
|
+
const joined = this.#provenanceJoin;
|
|
22146
|
+
if (joined !== null) this.#router.unprotectProvenanceFlow(joined.flow);
|
|
22147
|
+
this.#onFailback?.(reason);
|
|
22148
|
+
});
|
|
22149
|
+
}
|
|
22150
|
+
/**
|
|
22151
|
+
* A protected join's handoff (draft -01 7.2.7), before `onTakeover`: its
|
|
22152
|
+
* first admitted datagram. MoQ copies of the flow's tracks the router still
|
|
22153
|
+
* held unadmitted are discarded, and later ones are dropped. Fires again
|
|
22154
|
+
* after a failback once multicast resumes.
|
|
22155
|
+
*/
|
|
22156
|
+
onProtected(cb) {
|
|
22157
|
+
this.#router.onProvenanceFlowProtected(cb && ((flow) => {
|
|
22158
|
+
if (this.#provenanceJoin?.flow === flow) cb();
|
|
22159
|
+
}));
|
|
18950
22160
|
}
|
|
18951
22161
|
/** See MulticastTransport.onDataPlaneLive: the join's first accepted packet. */
|
|
18952
22162
|
onDataPlaneLive(cb) {
|
|
@@ -18962,6 +22172,12 @@ var MulticastManager = class {
|
|
|
18962
22172
|
return MulticastTransport.isAvailable();
|
|
18963
22173
|
}
|
|
18964
22174
|
async join(config) {
|
|
22175
|
+
const provenanceFlow = config.provenance?.flow;
|
|
22176
|
+
if (provenanceFlow !== void 0 && ((config.sourceAddress ?? config.senderAddress) !== provenanceFlow.source || config.groupAddress !== provenanceFlow.groupAddress || config.port !== provenanceFlow.port)) {
|
|
22177
|
+
throw new RangeError(
|
|
22178
|
+
"[MulticastManager] join: provenance flow (S, G, port) must match sourceAddress ?? senderAddress, groupAddress and port"
|
|
22179
|
+
);
|
|
22180
|
+
}
|
|
18965
22181
|
const staged = this.#renditionSwitch;
|
|
18966
22182
|
if (staged !== null) this.#endRenditionSwitch(staged, { reason: "superseded" }, false);
|
|
18967
22183
|
this.configureFec(config);
|
|
@@ -18969,16 +22185,21 @@ var MulticastManager = class {
|
|
|
18969
22185
|
const selectedPacketIds = config.packetIds?.length ? new Set(config.packetIds) : null;
|
|
18970
22186
|
const catalogTracks = config.tracks?.filter(({ packetId }) => selectedPacketIds === null || selectedPacketIds.has(packetId));
|
|
18971
22187
|
const ingressTracks = catalogTracks ? catalogTracks : selectedPacketIds === null ? void 0 : [...selectedPacketIds].map((packetId) => ({ name: "", packetId }));
|
|
18972
|
-
|
|
22188
|
+
this.#releaseProvenanceJoin();
|
|
22189
|
+
const provenance = config.provenance;
|
|
22190
|
+
const protectedTracks = ingressTracks ?? provenance?.flow.tracks;
|
|
22191
|
+
const ingress = protectedTracks === void 0 ? void 0 : {
|
|
18973
22192
|
tracks: Object.freeze(
|
|
18974
|
-
|
|
22193
|
+
protectedTracks.map(
|
|
18975
22194
|
({ name, packetId }) => Object.freeze({ name, packetId })
|
|
18976
22195
|
)
|
|
18977
|
-
)
|
|
22196
|
+
),
|
|
22197
|
+
...provenance === void 0 ? {} : { flow: multicastProvenanceFlowKey(provenance.flow) }
|
|
18978
22198
|
};
|
|
18979
22199
|
const amtRelay = config.amtRelay;
|
|
18980
22200
|
const transportConfig = {
|
|
18981
22201
|
sourceAddress: config.sourceAddress,
|
|
22202
|
+
senderAddress: config.senderAddress,
|
|
18982
22203
|
groupAddress: config.groupAddress,
|
|
18983
22204
|
port: config.port,
|
|
18984
22205
|
mode: config.mode,
|
|
@@ -19005,7 +22226,61 @@ var MulticastManager = class {
|
|
|
19005
22226
|
packetIds: config.packetIds,
|
|
19006
22227
|
tracks: catalogTracks?.map(({ name, packetId }) => ({ name, packetId }))
|
|
19007
22228
|
};
|
|
19008
|
-
|
|
22229
|
+
if (provenance !== void 0) this.#joinProvenance(provenance, ingress, transportConfig);
|
|
22230
|
+
const protectedJoin = this.#provenanceJoin;
|
|
22231
|
+
const joined = await (ingress === void 0 ? this.#transport.join(transportConfig) : this.#transport.join(transportConfig, ingress));
|
|
22232
|
+
if (!joined && protectedJoin !== null && this.#provenanceJoin === protectedJoin) this.#releaseProvenanceJoin();
|
|
22233
|
+
return joined;
|
|
22234
|
+
}
|
|
22235
|
+
/**
|
|
22236
|
+
* Register the protected flow and subscribe its `manifestPort` on the same
|
|
22237
|
+
* `(S, G)` with its own listener (draft -01 7.2.7), so records reach the
|
|
22238
|
+
* flow's receiver and media datagrams are held until one admits them. A
|
|
22239
|
+
* manifest subscription that fails to join admits nothing: the media is
|
|
22240
|
+
* held and expires, never read unauthenticated.
|
|
22241
|
+
*/
|
|
22242
|
+
#joinProvenance(provenance, ingress, media) {
|
|
22243
|
+
const { flow, auth } = provenance;
|
|
22244
|
+
const receiver = new BcProvenanceV2Receiver({
|
|
22245
|
+
...provenance.receiver,
|
|
22246
|
+
// The caller's ctx when it passes one, else the catalog's: an explicit
|
|
22247
|
+
// `catalogCtx: undefined` must not switch off the 7.2.4 ctx check.
|
|
22248
|
+
catalogCtx: provenance.receiver?.catalogCtx ?? flow.ctx,
|
|
22249
|
+
endpoint: {
|
|
22250
|
+
sourceAddress: flow.source,
|
|
22251
|
+
groupAddress: flow.groupAddress,
|
|
22252
|
+
port: flow.port,
|
|
22253
|
+
manifestPort: flow.manifestPort,
|
|
22254
|
+
streamId: flow.streamId
|
|
22255
|
+
},
|
|
22256
|
+
keys: auth.keys,
|
|
22257
|
+
minEpoch: auth.minEpoch,
|
|
22258
|
+
bandwidth: flow.bandwidth,
|
|
22259
|
+
maxAuthDelayMs: flow.maxAuthDelayMs,
|
|
22260
|
+
manifestTimerMs: flow.manifestTimerMs
|
|
22261
|
+
});
|
|
22262
|
+
this.#router.registerProvenanceFlow(ingress, receiver);
|
|
22263
|
+
this.#hookFailback();
|
|
22264
|
+
const manifest = new MulticastTransport((record) => {
|
|
22265
|
+
if (this.#provenanceJoin?.manifest === manifest) {
|
|
22266
|
+
this.#router.feedProvenanceRecord(ingress.flow, record, "multicast");
|
|
22267
|
+
}
|
|
22268
|
+
});
|
|
22269
|
+
this.#provenanceJoin = { flow: ingress.flow, manifest };
|
|
22270
|
+
const { packetIds: _packetIds, tracks: _tracks, ...path } = media;
|
|
22271
|
+
void manifest.join({ ...path, port: flow.manifestPort }).then((joined) => {
|
|
22272
|
+
if (!joined && this.#provenanceJoin?.manifest === manifest) {
|
|
22273
|
+
console.warn(`[MulticastManager] manifestPort ${flow.groupAddress}:${flow.manifestPort} not joined`, manifest.lastJoinOutcome);
|
|
22274
|
+
}
|
|
22275
|
+
});
|
|
22276
|
+
}
|
|
22277
|
+
/** Un-protect: drop the flow's hold queues (no expiry counted) and leave its manifestPort. */
|
|
22278
|
+
#releaseProvenanceJoin() {
|
|
22279
|
+
const joined = this.#provenanceJoin;
|
|
22280
|
+
if (joined === null) return;
|
|
22281
|
+
this.#provenanceJoin = null;
|
|
22282
|
+
joined.manifest.dispose();
|
|
22283
|
+
this.#router.releaseProvenanceFlow(joined.flow);
|
|
19009
22284
|
}
|
|
19010
22285
|
/**
|
|
19011
22286
|
* Join the selected rung's dedicated repair groups beside the media join
|
|
@@ -19035,10 +22310,11 @@ var MulticastManager = class {
|
|
|
19035
22310
|
this.#applyRepairOwners(repairOwners);
|
|
19036
22311
|
for (const endpoint of endpoints) {
|
|
19037
22312
|
const ingress = {
|
|
19038
|
-
tracks: Object.freeze(endpoint.tracks.map(({ name, packetId }) => Object.freeze({ name, packetId })))
|
|
22313
|
+
tracks: Object.freeze(endpoint.tracks.map(({ name, packetId }) => Object.freeze({ name, packetId }))),
|
|
22314
|
+
...endpoint.flow === void 0 ? {} : { flow: endpoint.flow }
|
|
19039
22315
|
};
|
|
19040
22316
|
const onPacket = (packet) => {
|
|
19041
|
-
if (!released) this.#router.feedPacket(packet, "multicast",
|
|
22317
|
+
if (!released) this.#router.feedPacket(packet, "multicast", ingress);
|
|
19042
22318
|
};
|
|
19043
22319
|
const fail = (error) => console.warn(`[MulticastManager] repair group ${endpoint.group}:${endpoint.port} not joined`, error);
|
|
19044
22320
|
if (typeof provider === "function") {
|
|
@@ -19176,7 +22452,7 @@ var MulticastManager = class {
|
|
|
19176
22452
|
for (const track of [...live.tracks, ...request.tracks]) {
|
|
19177
22453
|
stagedTracks.set(`${track.packetId}\0${track.name}`, track);
|
|
19178
22454
|
}
|
|
19179
|
-
const stagedIngress = freezeIngress(stagedTracks.values());
|
|
22455
|
+
const stagedIngress = freezeIngress(stagedTracks.values(), live.flow);
|
|
19180
22456
|
const stagedPacketIds = committedPacketIds === void 0 || livePacketIds === null ? null : /* @__PURE__ */ new Set([...livePacketIds, ...committedPacketIds]);
|
|
19181
22457
|
const widened = stagedPacketIds !== null && !sameIdSet(stagedPacketIds, livePacketIds);
|
|
19182
22458
|
const active = {
|
|
@@ -19187,7 +22463,7 @@ var MulticastManager = class {
|
|
|
19187
22463
|
liveIngress: live,
|
|
19188
22464
|
livePacketIds,
|
|
19189
22465
|
widened,
|
|
19190
|
-
committedIngress: freezeIngress(request.tracks),
|
|
22466
|
+
committedIngress: freezeIngress(request.tracks, live.flow),
|
|
19191
22467
|
committedPacketIds,
|
|
19192
22468
|
router: null,
|
|
19193
22469
|
state: "staged"
|
|
@@ -19200,7 +22476,7 @@ var MulticastManager = class {
|
|
|
19200
22476
|
let widening;
|
|
19201
22477
|
try {
|
|
19202
22478
|
if (widened) widening = this.#transport.setPacketIds([...stagedPacketIds]);
|
|
19203
|
-
this.#
|
|
22479
|
+
this.#setSwitchIngress(stagedIngress);
|
|
19204
22480
|
} catch (error) {
|
|
19205
22481
|
active.router.cancel();
|
|
19206
22482
|
throw error;
|
|
@@ -19221,6 +22497,11 @@ var MulticastManager = class {
|
|
|
19221
22497
|
}
|
|
19222
22498
|
};
|
|
19223
22499
|
}
|
|
22500
|
+
/** Route a switch's ingress and keep a protected flow's registration in step (7.2.7). */
|
|
22501
|
+
#setSwitchIngress(ingress) {
|
|
22502
|
+
this.#transport.setContext(ingress);
|
|
22503
|
+
if (ingress.flow !== void 0) this.#router.updateProvenanceFlowIngress({ ...ingress, flow: ingress.flow });
|
|
22504
|
+
}
|
|
19224
22505
|
/** The router committed the staged switch (it already retired `from`). */
|
|
19225
22506
|
#commitRenditionSwitch(active, commit) {
|
|
19226
22507
|
if (this.#renditionSwitch !== active) return;
|
|
@@ -19228,7 +22509,7 @@ var MulticastManager = class {
|
|
|
19228
22509
|
active.state = "committed";
|
|
19229
22510
|
active.router = null;
|
|
19230
22511
|
if (this.#transport.joined) {
|
|
19231
|
-
this.#
|
|
22512
|
+
this.#setSwitchIngress(active.committedIngress);
|
|
19232
22513
|
if (active.committedPacketIds !== void 0) {
|
|
19233
22514
|
this.#transport.setPacketIds(active.committedPacketIds).catch((error) => {
|
|
19234
22515
|
console.warn(
|
|
@@ -19264,7 +22545,7 @@ var MulticastManager = class {
|
|
|
19264
22545
|
active.router = null;
|
|
19265
22546
|
if (restore) {
|
|
19266
22547
|
if (this.#transport.joined) {
|
|
19267
|
-
this.#
|
|
22548
|
+
this.#setSwitchIngress(active.liveIngress);
|
|
19268
22549
|
if (active.widened && active.livePacketIds !== null) {
|
|
19269
22550
|
this.#transport.setPacketIds([...active.livePacketIds]).catch((error) => {
|
|
19270
22551
|
console.warn(
|
|
@@ -19336,11 +22617,11 @@ var MulticastManager = class {
|
|
|
19336
22617
|
}
|
|
19337
22618
|
/** Route a Worker-recovered FEC block through the MMTP parser + dedup pipeline. */
|
|
19338
22619
|
routeRecoveredBlock(blockId, data, packetId, catalogTrackName) {
|
|
19339
|
-
this.#router.routeRecoveredBlock(blockId, data, packetId, catalogTrackName);
|
|
22620
|
+
return this.#router.routeRecoveredBlock(blockId, data, packetId, catalogTrackName);
|
|
19340
22621
|
}
|
|
19341
22622
|
/** Route one Worker-recovered source symbol through the MMTP parser + dedup pipeline. */
|
|
19342
22623
|
routeRecoveredSymbol(blockId, data, packetId, esi = 0, catalogTrackName) {
|
|
19343
|
-
this.#router.routeRecoveredSymbol(blockId, data, packetId, esi, catalogTrackName);
|
|
22624
|
+
return this.#router.routeRecoveredSymbol(blockId, data, packetId, esi, catalogTrackName);
|
|
19344
22625
|
}
|
|
19345
22626
|
// ---------------------------------------------------------------------------
|
|
19346
22627
|
// Phase 14: multi-track catalog ingest (D-04, D-06, D-07, D-17, D-26, D-28)
|
|
@@ -19478,6 +22759,14 @@ var MulticastManager = class {
|
|
|
19478
22759
|
}
|
|
19479
22760
|
}
|
|
19480
22761
|
}
|
|
22762
|
+
const factory = typeof wasmDecoderSource === "function" ? wasmDecoderSource : null;
|
|
22763
|
+
const fecTracks = fecTrackIndexes.map((i) => catalog.tracks[i]);
|
|
22764
|
+
this.#router.setScratchFecDecoderFactory(factory && ((ownerPacketId, catalogTrackName) => {
|
|
22765
|
+
const track = fecTracks.find((t) => t.packetId === ownerPacketId && t.name === catalogTrackName);
|
|
22766
|
+
if (!track) return null;
|
|
22767
|
+
const decoder = factory(track);
|
|
22768
|
+
return [...fecDecoders.values()].includes(decoder) ? null : decoder;
|
|
22769
|
+
}));
|
|
19481
22770
|
this.#switchingSets.clear();
|
|
19482
22771
|
this.#repairPacketIds.clear();
|
|
19483
22772
|
this.#router.clearRepairPacketIdOwners();
|
|
@@ -19650,6 +22939,8 @@ var MulticastManager = class {
|
|
|
19650
22939
|
if (reason === "rtt-unmeasured") stats.repairsSkippedRttUnmeasured++;
|
|
19651
22940
|
else if (reason === "rtt-exceeds-budget") stats.repairsSkippedRttExceedsBudget++;
|
|
19652
22941
|
else if (reason === "range-no-anchor") stats.repairsSkippedRangeNoAnchor++;
|
|
22942
|
+
else if (reason === "group-cannot-complete") stats.repairsSkippedGroupCannotComplete++;
|
|
22943
|
+
else if (reason === "moot-rap") stats.repairsSkippedMootRap++;
|
|
19653
22944
|
else stats.repairsSkippedPresentationDelayUnavailable++;
|
|
19654
22945
|
stats.lastRepairSkip = {
|
|
19655
22946
|
reason,
|
|
@@ -19710,6 +23001,27 @@ var MulticastManager = class {
|
|
|
19710
23001
|
block: repairBlockIdentity(packetId, sbn, catalogTrackName)
|
|
19711
23002
|
});
|
|
19712
23003
|
}
|
|
23004
|
+
/** Record a running repair FETCH the scheduler stopped, and why. */
|
|
23005
|
+
noteRepairStopped(reason, sbn, packetId, catalogTrackName) {
|
|
23006
|
+
this.#statsFor(packetId, catalogTrackName).repairStops[reason]++;
|
|
23007
|
+
this.#emitRepairLifecycle({
|
|
23008
|
+
version: 2,
|
|
23009
|
+
type: "repair-stopped",
|
|
23010
|
+
block: repairBlockIdentity(packetId, sbn, catalogTrackName),
|
|
23011
|
+
reason
|
|
23012
|
+
});
|
|
23013
|
+
}
|
|
23014
|
+
/**
|
|
23015
|
+
* Whether a complete RAP above SS_ID `ssId` reached the stream's in-order
|
|
23016
|
+
* release gate (draft-ramadan-moq-fec 11.4.2: repair below it is moot).
|
|
23017
|
+
*/
|
|
23018
|
+
rapCompleteAbove(packetId, catalogTrackName, ssId) {
|
|
23019
|
+
return this.#router.fecReleaseRapAbove(packetId, catalogTrackName, ssId);
|
|
23020
|
+
}
|
|
23021
|
+
/** Observe each complete RAP reaching a release gate (synchronous: defer FEC work). */
|
|
23022
|
+
addRapCompleteObserver(observer) {
|
|
23023
|
+
return this.#router.addFecRapObserver(observer);
|
|
23024
|
+
}
|
|
19713
23025
|
/** Catalog/reset epoch of a packet's streams; gap decisions carry it. */
|
|
19714
23026
|
epochFor(packetId) {
|
|
19715
23027
|
return this.#fec.epochFor(packetId);
|
|
@@ -19725,6 +23037,7 @@ var MulticastManager = class {
|
|
|
19725
23037
|
if (stats.epoch !== this.#fec.epochFor(stats.packetId)) continue;
|
|
19726
23038
|
out[key] = {
|
|
19727
23039
|
...stats,
|
|
23040
|
+
repairStops: { ...stats.repairStops },
|
|
19728
23041
|
lastRepairSkip: stats.lastRepairSkip === null ? null : { ...stats.lastRepairSkip }
|
|
19729
23042
|
};
|
|
19730
23043
|
}
|
|
@@ -19845,16 +23158,33 @@ var MulticastManager = class {
|
|
|
19845
23158
|
this.#trackResetObservers.clear();
|
|
19846
23159
|
this.#repairLifecycleObservers.clear();
|
|
19847
23160
|
this.#transport.dispose();
|
|
23161
|
+
this.#releaseProvenanceJoin();
|
|
19848
23162
|
for (const release of [...this.#repairReleases.keys()]) release();
|
|
19849
23163
|
this.#router.dispose();
|
|
19850
23164
|
this.#fec.dispose();
|
|
19851
23165
|
}
|
|
19852
23166
|
};
|
|
23167
|
+
function multicastProvenanceFlowKey(flow) {
|
|
23168
|
+
return JSON.stringify([flow.source, flow.groupAddress, flow.port]);
|
|
23169
|
+
}
|
|
19853
23170
|
export {
|
|
19854
23171
|
MulticastManager,
|
|
19855
23172
|
REPAIR_LIFECYCLE_EVENT_TYPE,
|
|
19856
23173
|
REPAIR_LIFECYCLE_LOG_PREFIX,
|
|
19857
23174
|
forwardRepairLifecycleEvents,
|
|
19858
|
-
isRepairLifecycleEvent
|
|
23175
|
+
isRepairLifecycleEvent,
|
|
23176
|
+
multicastProvenanceFlowKey
|
|
19859
23177
|
};
|
|
23178
|
+
/*! Bundled license information:
|
|
23179
|
+
|
|
23180
|
+
@noble/hashes/esm/utils.js:
|
|
23181
|
+
(*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
|
|
23182
|
+
|
|
23183
|
+
@noble/curves/esm/utils.js:
|
|
23184
|
+
@noble/curves/esm/abstract/modular.js:
|
|
23185
|
+
@noble/curves/esm/abstract/curve.js:
|
|
23186
|
+
@noble/curves/esm/abstract/edwards.js:
|
|
23187
|
+
@noble/curves/esm/ed25519.js:
|
|
23188
|
+
(*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
|
|
23189
|
+
*/
|
|
19860
23190
|
//# sourceMappingURL=multicast-manager.js.map
|