@blockcast/mmt-container 2.1.0 → 2.2.0-main.51d1358fdf4c
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/dist/browser-runtime.js +446 -58
- package/dist/browser-runtime.js.map +4 -4
- package/dist/cmcd-producer.d.ts +5 -0
- package/dist/cmcd-producer.d.ts.map +1 -1
- package/dist/cmcd-producer.js.map +2 -2
- 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.no-modules.js +121 -15
- package/dist/mmtp-container-parser.no-modules.js.map +3 -3
- package/dist/mmtp-fec-session.d.ts +41 -3
- package/dist/mmtp-fec-session.d.ts.map +1 -1
- package/dist/mmtp-fec-session.js +118 -13
- package/dist/mmtp-fec-session.js.map +3 -3
- package/dist/mmtp-router.d.ts +9 -1
- package/dist/mmtp-router.d.ts.map +1 -1
- package/dist/mmtp-router.js +1456 -14
- package/dist/mmtp-router.js.map +4 -4
- package/dist/multicast-manager.d.ts +15 -2
- package/dist/multicast-manager.d.ts.map +1 -1
- package/dist/multicast-manager.js +15235 -14951
- package/dist/multicast-manager.js.map +4 -4
- package/dist/multicast-transport.d.ts +11 -3
- package/dist/multicast-transport.d.ts.map +1 -1
- package/dist/multicast-transport.js +255 -19
- package/dist/multicast-transport.js.map +4 -4
- package/dist/no-modules.js +118 -14
- package/dist/no-modules.js.map +3 -3
- package/dist/signaling.js.map +2 -2
- package/package.json +5 -5
- package/src/cmcd-producer.ts +5 -0
- package/src/index.ts +2 -0
- package/src/mmtp-fec-session.ts +115 -15
- package/src/mmtp-router.ts +81 -16
- package/src/multicast-manager.ts +26 -5
- package/src/multicast-transport.ts +44 -20
package/dist/browser-runtime.js
CHANGED
|
@@ -489,6 +489,8 @@ var PACKET_TYPE_SIGNALING = 2;
|
|
|
489
489
|
var PACKET_TYPE_REPAIR = 3;
|
|
490
490
|
var MMTP_SIGNALING_PACKET_ID = 0;
|
|
491
491
|
var MMTP_PACKET_ID_MAX = 65535;
|
|
492
|
+
var FEC_CODE_REED_SOLOMON = 1;
|
|
493
|
+
var FEC_CODE_RAPTORQ = 3;
|
|
492
494
|
function parseMMTPHeader(buffer) {
|
|
493
495
|
if (buffer.length < MMTP_HEADER_SIZE) {
|
|
494
496
|
throw new Error(`MMTP packet too small: ${buffer.length} bytes`);
|
|
@@ -903,6 +905,50 @@ var NTP_SHORT_BITS = 32;
|
|
|
903
905
|
var NTP_SHORT_MODULUS = 2 ** NTP_SHORT_BITS;
|
|
904
906
|
var NTP_SHORT_HALF_RANGE = 2 ** (NTP_SHORT_BITS - 1);
|
|
905
907
|
|
|
908
|
+
// ../mmt-base/dist/signalling.js
|
|
909
|
+
var FEC_CODING_STRUCTURE_ONE_STAGE = 1;
|
|
910
|
+
var SSBG_MODE_1 = 1;
|
|
911
|
+
var RAPTORQ_MAX_SOURCE_SYMBOLS = 56403;
|
|
912
|
+
var REPAIR_SYMBOL_ID_MAX = 16777215;
|
|
913
|
+
var MIN_REPAIR_SYMBOL_SIZE = 14;
|
|
914
|
+
var REED_SOLOMON_MAX_BLOCK = 255;
|
|
915
|
+
function alFecRaptorQFlowConfig(flow) {
|
|
916
|
+
if (flow.fecCodingStructure !== FEC_CODING_STRUCTURE_ONE_STAGE || flow.repairFlows.length !== 1)
|
|
917
|
+
return null;
|
|
918
|
+
if (flow.ssbgMode !== SSBG_MODE_1 || flow.ffsrptsFlag !== 0 || flow.fecPayloadIdMode !== 0)
|
|
919
|
+
return null;
|
|
920
|
+
if (flow.packetIds.length !== 1)
|
|
921
|
+
return null;
|
|
922
|
+
const repair = flow.repairFlows[0];
|
|
923
|
+
const { fecCodeId, maximumK: k, maximumP: p } = repair;
|
|
924
|
+
const t = flow.repairSymbolSize;
|
|
925
|
+
if (t < MIN_REPAIR_SYMBOL_SIZE || k < 1 || p < 1 || k + p - 1 > REPAIR_SYMBOL_ID_MAX)
|
|
926
|
+
return null;
|
|
927
|
+
if (fecCodeId === FEC_CODE_RAPTORQ) {
|
|
928
|
+
if (t % 8 !== 0 || k > RAPTORQ_MAX_SOURCE_SYMBOLS)
|
|
929
|
+
return null;
|
|
930
|
+
} else if (fecCodeId === FEC_CODE_REED_SOLOMON) {
|
|
931
|
+
if (k + p > REED_SOLOMON_MAX_BLOCK)
|
|
932
|
+
return null;
|
|
933
|
+
} else {
|
|
934
|
+
return null;
|
|
935
|
+
}
|
|
936
|
+
if (repair.repairFlowId === MMTP_SIGNALING_PACKET_ID || repair.repairFlowId === flow.packetIds[0])
|
|
937
|
+
return null;
|
|
938
|
+
return {
|
|
939
|
+
sourcePacketIds: [...flow.packetIds],
|
|
940
|
+
repairFlowId: repair.repairFlowId,
|
|
941
|
+
fecCodeId,
|
|
942
|
+
ssbgMode: flow.ssbgMode,
|
|
943
|
+
fecPayloadIdMode: flow.fecPayloadIdMode,
|
|
944
|
+
repairSymbolSize: t,
|
|
945
|
+
maximumK: k,
|
|
946
|
+
maximumP: p,
|
|
947
|
+
protectionWindowTime: repair.protectionWindowTime,
|
|
948
|
+
protectionWindowSize: repair.protectionWindowSize
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
|
|
906
952
|
// ../mmt-base/dist/serial-number.js
|
|
907
953
|
var SERIAL32_SPAN = 4294967296;
|
|
908
954
|
var SERIAL32_HALF = 2147483648;
|
|
@@ -7457,11 +7503,33 @@ var AdaptiveBitrateController = class {
|
|
|
7457
7503
|
};
|
|
7458
7504
|
|
|
7459
7505
|
// node_modules/@blockcast/mmt-base/dist/mmtp.js
|
|
7506
|
+
var MMTP_HEADER_SIZE2 = 12;
|
|
7460
7507
|
var MPU_HEADER_SIZE2 = 8;
|
|
7461
7508
|
var MPU_LENGTH_FIELD_SIZE2 = 2;
|
|
7462
7509
|
var MPU_HEADER_REMAINDER_SIZE2 = MPU_HEADER_SIZE2 - MPU_LENGTH_FIELD_SIZE2;
|
|
7463
7510
|
var MMTP_SIGNALING_PACKET_ID2 = 0;
|
|
7464
7511
|
var MMTP_PACKET_ID_MAX2 = 65535;
|
|
7512
|
+
function readMMTPPacketId2(buffer) {
|
|
7513
|
+
if (buffer.length < MMTP_HEADER_SIZE2)
|
|
7514
|
+
return null;
|
|
7515
|
+
const flags = buffer[0];
|
|
7516
|
+
if ((flags >> 6 & 3) !== 0)
|
|
7517
|
+
return null;
|
|
7518
|
+
let offset = MMTP_HEADER_SIZE2;
|
|
7519
|
+
if ((flags & 32) !== 0) {
|
|
7520
|
+
if (offset + 4 > buffer.length)
|
|
7521
|
+
return null;
|
|
7522
|
+
offset += 4;
|
|
7523
|
+
}
|
|
7524
|
+
if ((flags & 2) !== 0) {
|
|
7525
|
+
if (offset + 4 > buffer.length)
|
|
7526
|
+
return null;
|
|
7527
|
+
const length = buffer[offset + 2] << 8 | buffer[offset + 3];
|
|
7528
|
+
if (offset + 4 + length > buffer.length)
|
|
7529
|
+
return null;
|
|
7530
|
+
}
|
|
7531
|
+
return buffer[2] << 8 | buffer[3];
|
|
7532
|
+
}
|
|
7465
7533
|
|
|
7466
7534
|
// node_modules/@blockcast/mmt-base/dist/net-constants.js
|
|
7467
7535
|
var DEFAULT_AMT_RELAY_PORT2 = 2268;
|
|
@@ -8447,6 +8515,205 @@ var TransportManager = class {
|
|
|
8447
8515
|
};
|
|
8448
8516
|
var transportManager = new TransportManager();
|
|
8449
8517
|
|
|
8518
|
+
// node_modules/@blockcast/mmt-transport/dist/multicast-receiver-gateway.js
|
|
8519
|
+
var REGISTRY = /* @__PURE__ */ Symbol.for("@blockcast/mmt-transport/native-provider-registry/v1");
|
|
8520
|
+
var USER_AGENT_VERIFIED = /* @__PURE__ */ Symbol.for("@blockcast/mmt-transport/user-agent-verified/v1");
|
|
8521
|
+
var INTEGRITY_SCHEME = "manifest-ed25519-2";
|
|
8522
|
+
var MulticastError = class extends Error {
|
|
8523
|
+
code;
|
|
8524
|
+
constructor(code, message, cause) {
|
|
8525
|
+
super(message, cause === void 0 ? void 0 : { cause });
|
|
8526
|
+
this.name = "MulticastError";
|
|
8527
|
+
this.code = code;
|
|
8528
|
+
}
|
|
8529
|
+
};
|
|
8530
|
+
function datagramsOf(chunk) {
|
|
8531
|
+
const list = Array.isArray(chunk) ? chunk : chunk?.datagrams ?? [];
|
|
8532
|
+
const out = [];
|
|
8533
|
+
for (const d of list) {
|
|
8534
|
+
const data = d instanceof Uint8Array || d instanceof ArrayBuffer ? d : d?.data;
|
|
8535
|
+
if (data instanceof Uint8Array)
|
|
8536
|
+
out.push(data);
|
|
8537
|
+
else if (data instanceof ArrayBuffer)
|
|
8538
|
+
out.push(new Uint8Array(data));
|
|
8539
|
+
}
|
|
8540
|
+
return out;
|
|
8541
|
+
}
|
|
8542
|
+
function reasonOf(value) {
|
|
8543
|
+
return typeof value === "object" && value !== null && "reason" in value ? value.reason : value;
|
|
8544
|
+
}
|
|
8545
|
+
function registry() {
|
|
8546
|
+
const g = globalThis;
|
|
8547
|
+
if (g[REGISTRY] === void 0) {
|
|
8548
|
+
const providers = /* @__PURE__ */ new WeakSet();
|
|
8549
|
+
const byScope = /* @__PURE__ */ new WeakMap();
|
|
8550
|
+
Object.defineProperty(g, REGISTRY, {
|
|
8551
|
+
value: Object.freeze({
|
|
8552
|
+
isProvider: (x) => typeof x === "object" && x !== null && providers.has(x),
|
|
8553
|
+
providerFor: (scope) => {
|
|
8554
|
+
let provider = byScope.get(scope);
|
|
8555
|
+
if (provider === void 0) {
|
|
8556
|
+
provider = createProvider(scope);
|
|
8557
|
+
providers.add(provider);
|
|
8558
|
+
byScope.set(scope, provider);
|
|
8559
|
+
}
|
|
8560
|
+
return provider;
|
|
8561
|
+
}
|
|
8562
|
+
}),
|
|
8563
|
+
writable: false,
|
|
8564
|
+
configurable: false,
|
|
8565
|
+
enumerable: false
|
|
8566
|
+
});
|
|
8567
|
+
}
|
|
8568
|
+
return g[REGISTRY];
|
|
8569
|
+
}
|
|
8570
|
+
function resolveMulticastGateway(scope = globalThis) {
|
|
8571
|
+
const s = scope;
|
|
8572
|
+
if (typeof s.MulticastReceiver === "function")
|
|
8573
|
+
return registry().providerFor(scope);
|
|
8574
|
+
if (typeof s.multicast?.subscribe === "function")
|
|
8575
|
+
return s.multicast;
|
|
8576
|
+
return void 0;
|
|
8577
|
+
}
|
|
8578
|
+
function isUserAgentVerifiedGateway(gateway) {
|
|
8579
|
+
return registry().isProvider(gateway);
|
|
8580
|
+
}
|
|
8581
|
+
function userAgentVerifiedIngress(ingress, gateway) {
|
|
8582
|
+
if (isUserAgentVerifiedGateway(gateway))
|
|
8583
|
+
return { ...ingress, admittedBy: USER_AGENT_VERIFIED };
|
|
8584
|
+
if (!("admittedBy" in ingress))
|
|
8585
|
+
return ingress;
|
|
8586
|
+
const { admittedBy: _, ...rest } = ingress;
|
|
8587
|
+
return rest;
|
|
8588
|
+
}
|
|
8589
|
+
function unsupported(message, cause) {
|
|
8590
|
+
return new MulticastError("capability-unsupported", message, cause);
|
|
8591
|
+
}
|
|
8592
|
+
function createProvider(scope) {
|
|
8593
|
+
const Receiver = scope.MulticastReceiver;
|
|
8594
|
+
let gate = null;
|
|
8595
|
+
return {
|
|
8596
|
+
capabilities: () => Promise.resolve({ consumeModes: ["bytes"] }),
|
|
8597
|
+
subscribe: async (config) => {
|
|
8598
|
+
const c = config;
|
|
8599
|
+
if (c.consume !== "bytes")
|
|
8600
|
+
throw unsupported(`consume mode ${String(c.consume)} is not supported`);
|
|
8601
|
+
const origin = c.origin ?? "ip-multicast";
|
|
8602
|
+
if (origin !== "ip-multicast" && !(origin === "amt" && c.discovery === "driad")) {
|
|
8603
|
+
throw unsupported(`origin ${String(origin)} is not supported by MulticastReceiver`);
|
|
8604
|
+
}
|
|
8605
|
+
if (c.relay !== void 0)
|
|
8606
|
+
throw unsupported("MulticastReceiver picks its own AMT relay");
|
|
8607
|
+
if (!c.source)
|
|
8608
|
+
throw unsupported("MulticastReceiver needs an SSM source");
|
|
8609
|
+
if (c.senderAddress !== void 0 && c.senderAddress !== c.source) {
|
|
8610
|
+
throw unsupported("MulticastReceiver filters by source only");
|
|
8611
|
+
}
|
|
8612
|
+
const options = { sourceAddress: c.source, groupAddress: c.group, localPort: c.port };
|
|
8613
|
+
if (gate !== null)
|
|
8614
|
+
await gate;
|
|
8615
|
+
let rx;
|
|
8616
|
+
try {
|
|
8617
|
+
rx = new Receiver(options);
|
|
8618
|
+
} catch (error) {
|
|
8619
|
+
throw unsupported("MulticastReceiver refused the request", error);
|
|
8620
|
+
}
|
|
8621
|
+
const opened = rx.opened;
|
|
8622
|
+
if (gate === null)
|
|
8623
|
+
gate = opened.then(() => void 0, () => void 0);
|
|
8624
|
+
return openSubscription(rx, opened, options, c.packetIds);
|
|
8625
|
+
}
|
|
8626
|
+
};
|
|
8627
|
+
}
|
|
8628
|
+
async function openSubscription(rx, openedPromise, options, packetIds) {
|
|
8629
|
+
let opened;
|
|
8630
|
+
try {
|
|
8631
|
+
opened = await openedPromise;
|
|
8632
|
+
} catch (error) {
|
|
8633
|
+
throw new MulticastError("subscription-failed", "MulticastReceiver did not open", error);
|
|
8634
|
+
}
|
|
8635
|
+
if (rx.integrityScheme !== INTEGRITY_SCHEME) {
|
|
8636
|
+
await rx.close();
|
|
8637
|
+
throw unsupported(`integrity scheme ${String(rx.integrityScheme)} is not ${INTEGRITY_SCHEME}`);
|
|
8638
|
+
}
|
|
8639
|
+
const events = new EventTarget();
|
|
8640
|
+
let state = "connected";
|
|
8641
|
+
let filter = packetIds?.length ? new Set(packetIds) : null;
|
|
8642
|
+
let closing = false;
|
|
8643
|
+
const setState = (next) => {
|
|
8644
|
+
if (state === next)
|
|
8645
|
+
return;
|
|
8646
|
+
state = next;
|
|
8647
|
+
events.dispatchEvent(new CustomEvent("state", { detail: next }));
|
|
8648
|
+
};
|
|
8649
|
+
const sourceAddress = options.sourceAddress;
|
|
8650
|
+
const subscription = {
|
|
8651
|
+
id: `${options.sourceAddress}|${options.groupAddress}|${options.localPort}`,
|
|
8652
|
+
get state() {
|
|
8653
|
+
return state;
|
|
8654
|
+
},
|
|
8655
|
+
delivery: "datagram",
|
|
8656
|
+
eventTypes: Object.freeze(["bytes", "packets", "state", "error"]),
|
|
8657
|
+
setPacketIds(ids) {
|
|
8658
|
+
filter = ids.length ? new Set(ids) : null;
|
|
8659
|
+
},
|
|
8660
|
+
addEventListener: events.addEventListener.bind(events),
|
|
8661
|
+
removeEventListener: events.removeEventListener.bind(events),
|
|
8662
|
+
async unsubscribe() {
|
|
8663
|
+
closing = true;
|
|
8664
|
+
await rx.close();
|
|
8665
|
+
setState("closed");
|
|
8666
|
+
}
|
|
8667
|
+
};
|
|
8668
|
+
void (async () => {
|
|
8669
|
+
const reader = opened.readable.getReader();
|
|
8670
|
+
try {
|
|
8671
|
+
for (; ; ) {
|
|
8672
|
+
const { value, done } = await reader.read();
|
|
8673
|
+
if (done)
|
|
8674
|
+
break;
|
|
8675
|
+
const packets = [];
|
|
8676
|
+
for (const data of datagramsOf(value)) {
|
|
8677
|
+
if (filter !== null) {
|
|
8678
|
+
const id = readMMTPPacketId2(data);
|
|
8679
|
+
if (id === null || !filter.has(id))
|
|
8680
|
+
continue;
|
|
8681
|
+
}
|
|
8682
|
+
const copy = data.slice().buffer;
|
|
8683
|
+
packets.push(copy);
|
|
8684
|
+
events.dispatchEvent(Object.assign(new CustomEvent("bytes", { detail: copy }), { sourceAddress }));
|
|
8685
|
+
}
|
|
8686
|
+
if (packets.length > 0)
|
|
8687
|
+
events.dispatchEvent(new CustomEvent("packets", { detail: packets }));
|
|
8688
|
+
}
|
|
8689
|
+
} catch {
|
|
8690
|
+
}
|
|
8691
|
+
})();
|
|
8692
|
+
void rx.closed.then((value) => {
|
|
8693
|
+
const reason = reasonOf(value);
|
|
8694
|
+
if (!closing) {
|
|
8695
|
+
if (reason === "consent-withdrawn") {
|
|
8696
|
+
events.dispatchEvent(new CustomEvent("error", {
|
|
8697
|
+
detail: new MulticastError("origin-not-trusted", "multicast consent withdrawn")
|
|
8698
|
+
}));
|
|
8699
|
+
} else if (reason === "oversubscribed" || reason === "network") {
|
|
8700
|
+
events.dispatchEvent(new CustomEvent("error", {
|
|
8701
|
+
detail: new MulticastError("subscription-failed", `MulticastReceiver closed: ${reason}`)
|
|
8702
|
+
}));
|
|
8703
|
+
}
|
|
8704
|
+
}
|
|
8705
|
+
setState("closed");
|
|
8706
|
+
}, (error) => {
|
|
8707
|
+
if (!closing) {
|
|
8708
|
+
events.dispatchEvent(new CustomEvent("error", {
|
|
8709
|
+
detail: new MulticastError("subscription-failed", "MulticastReceiver failed", error)
|
|
8710
|
+
}));
|
|
8711
|
+
}
|
|
8712
|
+
setState("closed");
|
|
8713
|
+
});
|
|
8714
|
+
return subscription;
|
|
8715
|
+
}
|
|
8716
|
+
|
|
8450
8717
|
// node_modules/@blockcast/mmt-transport/dist/moqt-priority.js
|
|
8451
8718
|
var MAX_MOQT_PRIORITY = 255;
|
|
8452
8719
|
function moqtPriority(value, field) {
|
|
@@ -8733,9 +9000,9 @@ function validateTemplatePair(mediaUriTemplate, mediaObjectTemplate, path) {
|
|
|
8733
9000
|
}
|
|
8734
9001
|
function validateMmtpDashTemplate(template, path) {
|
|
8735
9002
|
const variables = [...template.matchAll(TEMPLATE_VARIABLE)].map((match) => match[1]);
|
|
8736
|
-
const
|
|
8737
|
-
if (
|
|
8738
|
-
throw new Error(`${path} contains unsupported MMTP-backed DASH template variable $${
|
|
9003
|
+
const unsupported2 = variables.find((variable) => variable !== "Number" && variable !== "RepresentationID");
|
|
9004
|
+
if (unsupported2 !== void 0) {
|
|
9005
|
+
throw new Error(`${path} contains unsupported MMTP-backed DASH template variable $${unsupported2}$`);
|
|
8739
9006
|
}
|
|
8740
9007
|
if (variables.filter((variable) => variable === "Number").length !== 1) {
|
|
8741
9008
|
throw new Error(`${path} must contain exactly one $Number$ for MMTP-backed DASH`);
|
|
@@ -17098,17 +17365,17 @@ var FECManager = class _FECManager {
|
|
|
17098
17365
|
|
|
17099
17366
|
// src/mfu-reassembly-policy.ts
|
|
17100
17367
|
function validateReliableMoqOptionShape(value) {
|
|
17101
|
-
const
|
|
17368
|
+
const unsupported2 = Object.keys(value).filter(
|
|
17102
17369
|
(key) => key !== "transport" && key !== "startsAtRandomAccessGroup"
|
|
17103
17370
|
);
|
|
17104
|
-
if (
|
|
17105
|
-
if (
|
|
17371
|
+
if (unsupported2.length > 0) {
|
|
17372
|
+
if (unsupported2.includes("maxRacingGroups")) {
|
|
17106
17373
|
throw new Error(
|
|
17107
17374
|
"[MFU reassembly] maxRacingGroups was removed; reliable MoQ completion is transport-owned"
|
|
17108
17375
|
);
|
|
17109
17376
|
}
|
|
17110
17377
|
throw new Error(
|
|
17111
|
-
`[MFU reassembly] unsupported reliable MoQ option(s): ${
|
|
17378
|
+
`[MFU reassembly] unsupported reliable MoQ option(s): ${unsupported2.join(", ")}`
|
|
17112
17379
|
);
|
|
17113
17380
|
}
|
|
17114
17381
|
const startsAtRandomAccessGroup = value["startsAtRandomAccessGroup"];
|
|
@@ -17152,12 +17419,12 @@ function assertValidMfuReassemblyPolicy(policy) {
|
|
|
17152
17419
|
return;
|
|
17153
17420
|
}
|
|
17154
17421
|
if (value["transport"] === "datagram") {
|
|
17155
|
-
const
|
|
17422
|
+
const unsupported2 = Object.keys(value).filter(
|
|
17156
17423
|
(key) => key !== "transport" && key !== "deadlineMs"
|
|
17157
17424
|
);
|
|
17158
|
-
if (
|
|
17425
|
+
if (unsupported2.length > 0) {
|
|
17159
17426
|
throw new Error(
|
|
17160
|
-
`[MFU reassembly] unsupported datagram option(s): ${
|
|
17427
|
+
`[MFU reassembly] unsupported datagram option(s): ${unsupported2.join(", ")}`
|
|
17161
17428
|
);
|
|
17162
17429
|
}
|
|
17163
17430
|
assertValidDatagramDeadline(value["deadlineMs"]);
|
|
@@ -23579,10 +23846,11 @@ var MmtpRouter = class {
|
|
|
23579
23846
|
if (!entry) return;
|
|
23580
23847
|
entry.receiver.release();
|
|
23581
23848
|
entry.protected = false;
|
|
23849
|
+
entry.userAgent = false;
|
|
23582
23850
|
this.#assistBlocks.clear();
|
|
23583
23851
|
this.#assistFailures.clear();
|
|
23584
23852
|
}
|
|
23585
|
-
/**
|
|
23853
|
+
/** Packets of a track a protected flow lists, dropped unread: a MoQ copy (duplicate, overflow, or listed by more than one flow) or a multicast source datagram that names no flow, of a track a user-agent-admitted flow lists. */
|
|
23586
23854
|
get unadmittedListedDropCount() {
|
|
23587
23855
|
return this.#unadmittedListedDrops;
|
|
23588
23856
|
}
|
|
@@ -23623,6 +23891,13 @@ var MmtpRouter = class {
|
|
|
23623
23891
|
}
|
|
23624
23892
|
return false;
|
|
23625
23893
|
}
|
|
23894
|
+
/** Whether a protected flow the user agent admitted lists `packetId`. */
|
|
23895
|
+
#userAgentGoverns(packetId) {
|
|
23896
|
+
for (const entry of this.#provenance.values()) {
|
|
23897
|
+
if (entry.protected && entry.userAgent && ingressHasPacketId(entry.ingress, packetId)) return true;
|
|
23898
|
+
}
|
|
23899
|
+
return false;
|
|
23900
|
+
}
|
|
23626
23901
|
/**
|
|
23627
23902
|
* A MoQ repair symbol no protected flow lists, owned by a source track one
|
|
23628
23903
|
* does: its FEC instance is governed (7.2.7 step 2) and takes only admitted
|
|
@@ -23713,7 +23988,7 @@ var MmtpRouter = class {
|
|
|
23713
23988
|
fec.cancelBlockTimeout(blockStart, ownerPacketId, catalogTrackName);
|
|
23714
23989
|
fec.countRecovery(ownerPacketId, blockStart, catalogTrackName);
|
|
23715
23990
|
fec.markBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
|
|
23716
|
-
})) {
|
|
23991
|
+
}, true)) {
|
|
23717
23992
|
this.#unlistedAssistedRecoveries++;
|
|
23718
23993
|
this.#assistBlocks.delete(key);
|
|
23719
23994
|
return;
|
|
@@ -24869,6 +25144,12 @@ var MmtpRouter = class {
|
|
|
24869
25144
|
debugRouter(`[MmtpRouter] Dropping multicast packet pid=${info.packetId}: flow ${ingress.flow} has no receiver`);
|
|
24870
25145
|
return;
|
|
24871
25146
|
}
|
|
25147
|
+
if (ingress.admittedBy === USER_AGENT_VERIFIED) {
|
|
25148
|
+
this.#noteFlowAdmitted(ingress.flow);
|
|
25149
|
+
protectedFlow.userAgent = true;
|
|
25150
|
+
this.#processMMTP(packet, source, true, ingress, overlapCopy);
|
|
25151
|
+
return;
|
|
25152
|
+
}
|
|
24872
25153
|
const { verdict } = protectedFlow.receiver.onDatagram(packet, "multicast");
|
|
24873
25154
|
if (verdict === "admitted") {
|
|
24874
25155
|
this.#noteFlowAdmitted(ingress.flow);
|
|
@@ -24886,6 +25167,11 @@ var MmtpRouter = class {
|
|
|
24886
25167
|
return;
|
|
24887
25168
|
}
|
|
24888
25169
|
}
|
|
25170
|
+
const flowless = source === "multicast" && !manifestAdmitted && ingress?.flow === void 0;
|
|
25171
|
+
if (flowless && !info.isRepair && this.#userAgentGoverns(info.packetId)) {
|
|
25172
|
+
this.#unadmittedListedDrops++;
|
|
25173
|
+
return;
|
|
25174
|
+
}
|
|
24889
25175
|
if (info.isRepair) {
|
|
24890
25176
|
const overlay = this.#overlayRepairTarget(info.packetId, catalogTrackNameForPacket(ingress, info.packetId));
|
|
24891
25177
|
if (overlay !== void 0) {
|
|
@@ -24903,7 +25189,7 @@ var MmtpRouter = class {
|
|
|
24903
25189
|
);
|
|
24904
25190
|
if (!repair) return;
|
|
24905
25191
|
if (ownerPacketId != null && this.#fecManager) {
|
|
24906
|
-
if (source === "moq" && !manifestAdmitted && this.#setAsideUnlistedRepair(ownerPacketId, catalogTrackNameForPacket(ingress, ownerPacketId), repair)) return;
|
|
25192
|
+
if ((flowless && this.#userAgentGoverns(ownerPacketId) || source === "moq" && !manifestAdmitted) && this.#setAsideUnlistedRepair(ownerPacketId, catalogTrackNameForPacket(ingress, ownerPacketId), repair)) return;
|
|
24907
25193
|
this.#repairPidCounts[info.packetId] = (this.#repairPidCounts[info.packetId] || 0) + 1;
|
|
24908
25194
|
const count = this.#repairPidCounts[info.packetId];
|
|
24909
25195
|
if (count <= 2 || count % 500 === 0) {
|
|
@@ -25482,7 +25768,7 @@ var MmtpRouter = class {
|
|
|
25482
25768
|
}
|
|
25483
25769
|
// ── Private: FEC recovery routing ───────────────────────────────
|
|
25484
25770
|
/** Route WASM-recovered block through appropriate path based on container kind. */
|
|
25485
|
-
#routeRecoveredBlock(blockId, recovered, ownerPacketId, catalogTrackName, complete) {
|
|
25771
|
+
#routeRecoveredBlock(blockId, recovered, ownerPacketId, catalogTrackName, complete, assisted = false) {
|
|
25486
25772
|
const packetSymbolSize = this.#symbolSizeForPacket(ownerPacketId);
|
|
25487
25773
|
if (ownerPacketId === void 0) {
|
|
25488
25774
|
throw new Error("[MmtpRouter] a recovered FEC block needs its owner packetId to read its fec.blockProfile");
|
|
@@ -25499,7 +25785,7 @@ var MmtpRouter = class {
|
|
|
25499
25785
|
ownerPacketId,
|
|
25500
25786
|
blockProfile
|
|
25501
25787
|
});
|
|
25502
|
-
return this.#routeVerifiedFecSymbols(blockId, result, ownerPacketId, catalogTrackName, complete);
|
|
25788
|
+
return this.#routeVerifiedFecSymbols(blockId, result, ownerPacketId, catalogTrackName, complete, assisted);
|
|
25503
25789
|
}
|
|
25504
25790
|
/**
|
|
25505
25791
|
* A recovered source packet of a protected flow is admitted on its own
|
|
@@ -25511,9 +25797,24 @@ var MmtpRouter = class {
|
|
|
25511
25797
|
* recovered packets is refused is unrecovered (7.2.7): its caller leaves it
|
|
25512
25798
|
* open, so it reaches its FEC deadline and fails by class. Always true
|
|
25513
25799
|
* outside a protected flow.
|
|
25514
|
-
|
|
25515
|
-
|
|
25516
|
-
|
|
25800
|
+
*
|
|
25801
|
+
* On a flow the user agent admitted, the live decoder's recoveries are kept
|
|
25802
|
+
* without a record: while the flow is protected that decoder takes only
|
|
25803
|
+
* symbols the user agent or a record verified (a flow-less multicast
|
|
25804
|
+
* datagram of a listed track is dropped or set aside in #processMMTP), so
|
|
25805
|
+
* what it recovers is as verified as they are. An `assisted` (scratch-decoder) recovery mixes in
|
|
25806
|
+
* unlisted MoQ repair and still needs a record.
|
|
25807
|
+
* ponytail: per-flow flag; per-block provenance only if a protected flow
|
|
25808
|
+
* ever mixes verified and unverified live inputs.
|
|
25809
|
+
* ponytail: a worker frame computed before the protection reset can still
|
|
25810
|
+
* arrive after it (frames carry no epoch); its inputs are MoQ copies read
|
|
25811
|
+
* and played before protection, so it admits nothing new. Drop pre-reset
|
|
25812
|
+
* frames by epoch only if pre-protection input ever matters.
|
|
25813
|
+
*/
|
|
25814
|
+
#routeVerifiedFecSymbols(blockId, result, ownerPacketId, catalogTrackName, complete, assisted = false) {
|
|
25815
|
+
const entry = ownerPacketId == null ? void 0 : this.#provenanceFor(ownerPacketId, catalogTrackName);
|
|
25816
|
+
const receiver = entry === void 0 || entry === "ambiguous" ? entry : entry.receiver;
|
|
25817
|
+
const userAgent = !assisted && entry !== void 0 && entry !== "ambiguous" && entry.protected === true && entry.userAgent === true;
|
|
25517
25818
|
let recovered = true;
|
|
25518
25819
|
if (receiver) {
|
|
25519
25820
|
const parsed = this.#stream(ownerPacketId, catalogTrackName).duplicates.parsedSymbols;
|
|
@@ -25525,6 +25826,8 @@ var MmtpRouter = class {
|
|
|
25525
25826
|
let admitted = false;
|
|
25526
25827
|
if (receiver === "ambiguous") {
|
|
25527
25828
|
this.#ambiguousRecoveredDrops++;
|
|
25829
|
+
} else if (userAgent) {
|
|
25830
|
+
admitted = true;
|
|
25528
25831
|
} else {
|
|
25529
25832
|
const wire = new Uint8Array(sym.rawSymbol.byteLength + 4);
|
|
25530
25833
|
wire.set(sym.rawSymbol);
|
|
@@ -25552,12 +25855,12 @@ var MmtpRouter = class {
|
|
|
25552
25855
|
catalogTrackName === void 0 || !byName ? byName?.values() : byName.has(catalogTrackName) ? [byName.get(catalogTrackName)] : []
|
|
25553
25856
|
);
|
|
25554
25857
|
if (flows.size > 1) return "ambiguous";
|
|
25555
|
-
for (const bound of flows) return this.#provenance.get(bound)
|
|
25858
|
+
for (const bound of flows) return this.#provenance.get(bound) ?? "ambiguous";
|
|
25556
25859
|
let match;
|
|
25557
|
-
for (const
|
|
25558
|
-
if (ingress.tracks.some((t) => t.packetId === packetId && (catalogTrackName === void 0 || t.name === catalogTrackName))) {
|
|
25860
|
+
for (const entry of this.#provenance.values()) {
|
|
25861
|
+
if (entry.ingress.tracks.some((t) => t.packetId === packetId && (catalogTrackName === void 0 || t.name === catalogTrackName))) {
|
|
25559
25862
|
if (match) return "ambiguous";
|
|
25560
|
-
match =
|
|
25863
|
+
match = entry;
|
|
25561
25864
|
}
|
|
25562
25865
|
}
|
|
25563
25866
|
return match;
|
|
@@ -26339,11 +26642,8 @@ function providerErrorOf(error) {
|
|
|
26339
26642
|
return Object.freeze(typeof code === "string" ? { name, message, code } : { name, message });
|
|
26340
26643
|
}
|
|
26341
26644
|
var MAX_TRACKED_DROPPED_PIDS = 16;
|
|
26342
|
-
function getMulticastGlobals() {
|
|
26343
|
-
return globalThis;
|
|
26344
|
-
}
|
|
26345
26645
|
function hasMulticastGateway() {
|
|
26346
|
-
return typeof
|
|
26646
|
+
return typeof resolveMulticastGateway()?.subscribe === "function";
|
|
26347
26647
|
}
|
|
26348
26648
|
function senderOf(event) {
|
|
26349
26649
|
const sourceAddress = event.sourceAddress;
|
|
@@ -26435,6 +26735,9 @@ var MulticastTransport = class {
|
|
|
26435
26735
|
// listener closure, so an in-place admission change (setContext) applies
|
|
26436
26736
|
// from the next packet on without rejoining.
|
|
26437
26737
|
#context = void 0;
|
|
26738
|
+
#deliveredContext = void 0;
|
|
26739
|
+
/** The live join's gateway, when it is the user agent's MulticastReceiver provider. */
|
|
26740
|
+
#userAgentGateway = void 0;
|
|
26438
26741
|
// The live join's provider-side filter. An in-place widening must reach it,
|
|
26439
26742
|
// or the provider never delivers the new ID; null without a live join.
|
|
26440
26743
|
#providerFilter = null;
|
|
@@ -26471,7 +26774,7 @@ var MulticastTransport = class {
|
|
|
26471
26774
|
this.#dataPlaneLive = false;
|
|
26472
26775
|
this.#sender = void 0;
|
|
26473
26776
|
if (this.#disposed) return this.#declined(generation, "disposed");
|
|
26474
|
-
const gateway = this.#gateway ??
|
|
26777
|
+
const gateway = this.#gateway ?? resolveMulticastGateway();
|
|
26475
26778
|
if (typeof gateway?.subscribe !== "function") {
|
|
26476
26779
|
console.warn("[MulticastTransport] Multicast API not available");
|
|
26477
26780
|
return this.#declined(generation, "unavailable");
|
|
@@ -26487,15 +26790,16 @@ var MulticastTransport = class {
|
|
|
26487
26790
|
console.log(
|
|
26488
26791
|
`[MulticastTransport] window.multicast.subscribe(${config.sourceAddress ?? config.senderAddress ?? "*"}, ${config.groupAddress}, ${config.port})`
|
|
26489
26792
|
);
|
|
26793
|
+
const userAgent = isUserAgentVerifiedGateway(gateway);
|
|
26490
26794
|
const subscribeConfig = toWindowMulticastSubscribeConfig({
|
|
26491
26795
|
source: config.sourceAddress,
|
|
26492
26796
|
senderAddress: config.senderAddress,
|
|
26493
26797
|
group: config.groupAddress,
|
|
26494
26798
|
port: config.port,
|
|
26495
|
-
mode: config.mode,
|
|
26496
|
-
amtRelay: config.amtRelay,
|
|
26497
|
-
amtRelayPort: config.amtRelayPort,
|
|
26498
|
-
amtDiscovery: config.amtDiscovery,
|
|
26799
|
+
mode: userAgent ? "native" : config.mode,
|
|
26800
|
+
amtRelay: userAgent ? void 0 : config.amtRelay,
|
|
26801
|
+
amtRelayPort: userAgent ? void 0 : config.amtRelayPort,
|
|
26802
|
+
amtDiscovery: userAgent ? void 0 : config.amtDiscovery,
|
|
26499
26803
|
packetIds: config.packetIds,
|
|
26500
26804
|
tracks: config.tracks,
|
|
26501
26805
|
atsc3: config.atsc3
|
|
@@ -26541,7 +26845,8 @@ var MulticastTransport = class {
|
|
|
26541
26845
|
acknowledgedUpdate: 0,
|
|
26542
26846
|
inFlight: 0
|
|
26543
26847
|
};
|
|
26544
|
-
this.#
|
|
26848
|
+
this.#userAgentGateway = userAgent ? gateway : void 0;
|
|
26849
|
+
this.#setContext(context);
|
|
26545
26850
|
if (packetIds) {
|
|
26546
26851
|
for (const packetId of packetIds) {
|
|
26547
26852
|
if (!this.#acceptedBytesByPacketId.has(packetId)) {
|
|
@@ -26549,7 +26854,7 @@ var MulticastTransport = class {
|
|
|
26549
26854
|
}
|
|
26550
26855
|
}
|
|
26551
26856
|
}
|
|
26552
|
-
this.#resolvedTransport = subscribeConfig.origin === "amt" ? "amt" : "native";
|
|
26857
|
+
this.#resolvedTransport = userAgent ? "user-agent" : subscribeConfig.origin === "amt" ? "amt" : "native";
|
|
26553
26858
|
const delivery = subscription.delivery ?? "datagram";
|
|
26554
26859
|
if (delivery !== "datagram" && delivery !== "ordered-relay") {
|
|
26555
26860
|
throw new Error(
|
|
@@ -26630,7 +26935,7 @@ var MulticastTransport = class {
|
|
|
26630
26935
|
}
|
|
26631
26936
|
if (!this.#acceptPacket(packet, sourceAddress)) return false;
|
|
26632
26937
|
const liveness = !this.#isRecordCandidate(packet);
|
|
26633
|
-
this.#onPacket(packet, this.#
|
|
26938
|
+
this.#onPacket(packet, this.#deliveredContext);
|
|
26634
26939
|
return liveness;
|
|
26635
26940
|
}
|
|
26636
26941
|
#isRecordCandidate(packet) {
|
|
@@ -26841,8 +27146,10 @@ var MulticastTransport = class {
|
|
|
26841
27146
|
return this.#subscription !== null;
|
|
26842
27147
|
}
|
|
26843
27148
|
/**
|
|
26844
|
-
*
|
|
26845
|
-
*
|
|
27149
|
+
* The live join's context as the caller gave it (the join's `context`, or
|
|
27150
|
+
* the last setContext), or undefined without a live join. On a native
|
|
27151
|
+
* provider each accepted packet is delivered with a marked copy of it
|
|
27152
|
+
* (`userAgentVerifiedIngress`); this getter never carries the mark.
|
|
26846
27153
|
*/
|
|
26847
27154
|
get context() {
|
|
26848
27155
|
return this.#context;
|
|
@@ -26866,7 +27173,16 @@ var MulticastTransport = class {
|
|
|
26866
27173
|
"[MulticastTransport] setContext needs a live join; join() carries the first context"
|
|
26867
27174
|
);
|
|
26868
27175
|
}
|
|
27176
|
+
this.#setContext(context);
|
|
27177
|
+
}
|
|
27178
|
+
/**
|
|
27179
|
+
* The join's context, and what the subscription delivers with each packet:
|
|
27180
|
+
* marked admitted by the user agent when the gateway is its native
|
|
27181
|
+
* MulticastReceiver provider. feedPacket never marks.
|
|
27182
|
+
*/
|
|
27183
|
+
#setContext(context) {
|
|
26869
27184
|
this.#context = context;
|
|
27185
|
+
this.#deliveredContext = this.#userAgentGateway !== void 0 && typeof context === "object" && context !== null ? userAgentVerifiedIngress(context, this.#userAgentGateway) : context;
|
|
26870
27186
|
}
|
|
26871
27187
|
/**
|
|
26872
27188
|
* Replace the live join's packet-ID allow-list in place, without rejoining.
|
|
@@ -26998,6 +27314,8 @@ var MulticastTransport = class {
|
|
|
26998
27314
|
this.#matchedSource = void 0;
|
|
26999
27315
|
this.#providerFilter = null;
|
|
27000
27316
|
this.#context = void 0;
|
|
27317
|
+
this.#deliveredContext = void 0;
|
|
27318
|
+
this.#userAgentGateway = void 0;
|
|
27001
27319
|
this.#withdrawnPacketIds.clear();
|
|
27002
27320
|
}
|
|
27003
27321
|
/** The ingress filter; an accepted packet is counted here, once. */
|
|
@@ -28350,8 +28668,17 @@ var MulticastManager = class {
|
|
|
28350
28668
|
* repair roles and owners are registered here; in-band owners stay the
|
|
28351
28669
|
* caller's. Joining is best effort: a group that fails to join is logged
|
|
28352
28670
|
* and leaves the media join as it was.
|
|
28353
|
-
|
|
28354
|
-
|
|
28671
|
+
*
|
|
28672
|
+
* `options.gateway` names the gateway a subscribe callback reads through.
|
|
28673
|
+
* When it is the browser's own MulticastReceiver provider, the callback's
|
|
28674
|
+
* packets are fed as admitted by the user agent
|
|
28675
|
+
* (`userAgentVerifiedIngress`); nothing the callback passes can mark them.
|
|
28676
|
+
* The mark rests on this statement, not on the data path: every packet the
|
|
28677
|
+
* callback delivers must come from a subscription opened on
|
|
28678
|
+
* `options.gateway`. A callback that also merges another source (a MoQ or
|
|
28679
|
+
* WebSocket repair feed) must not name a native provider here.
|
|
28680
|
+
*/
|
|
28681
|
+
joinRepairEndpoints(endpoints, provider, options) {
|
|
28355
28682
|
const releases = [];
|
|
28356
28683
|
let released = false;
|
|
28357
28684
|
const safely = (each) => {
|
|
@@ -28373,17 +28700,19 @@ var MulticastManager = class {
|
|
|
28373
28700
|
...endpoint.flow === void 0 ? {} : { flow: endpoint.flow }
|
|
28374
28701
|
};
|
|
28375
28702
|
let sender;
|
|
28376
|
-
const
|
|
28703
|
+
const feed = (packet, sourceAddress, ingressToFeed) => {
|
|
28377
28704
|
if (released) return;
|
|
28378
28705
|
if (sourceAddress !== void 0 && !sameDatagramSource(sourceAddress, this.#transport.sender ?? (sender ??= sourceAddress))) return;
|
|
28379
|
-
this.#router.feedPacket(packet, "multicast",
|
|
28706
|
+
this.#router.feedPacket(packet, "multicast", ingressToFeed);
|
|
28380
28707
|
};
|
|
28381
28708
|
const fail = (error) => console.warn(`[MulticastManager] repair group ${endpoint.group}:${endpoint.port} not joined`, error);
|
|
28382
28709
|
if (typeof provider === "function") {
|
|
28710
|
+
const marked = userAgentVerifiedIngress(ingress, options?.gateway);
|
|
28711
|
+
const onPacket = (packet, sourceAddress) => feed(packet, sourceAddress, marked);
|
|
28383
28712
|
Promise.resolve().then(() => provider(endpoint, onPacket)).then((release2) => released ? safely(release2) : releases.push(release2), fail);
|
|
28384
28713
|
continue;
|
|
28385
28714
|
}
|
|
28386
|
-
const transport = new MulticastTransport((packet) =>
|
|
28715
|
+
const transport = new MulticastTransport((packet, delivered) => feed(packet, void 0, delivered ?? ingress), {
|
|
28387
28716
|
gateway: provider,
|
|
28388
28717
|
lockedTo: () => this.#transport.sender
|
|
28389
28718
|
});
|
|
@@ -28395,11 +28724,12 @@ var MulticastManager = class {
|
|
|
28395
28724
|
mode: endpoint.mode,
|
|
28396
28725
|
amtRelay: endpoint.amtRelay,
|
|
28397
28726
|
amtRelayPort: endpoint.amtRelayPort ?? (endpoint.amtRelay?.trim() ? DEFAULT_AMT_RELAY_PORT : void 0),
|
|
28727
|
+
amtDiscovery: endpoint.amtDiscovery,
|
|
28398
28728
|
packetIds: endpoint.packetIds,
|
|
28399
28729
|
tracks: endpoint.tracks.map(({ name, packetId }) => ({ name, packetId })),
|
|
28400
28730
|
// A protected repair group's records ride in band on its port (7.2.3).
|
|
28401
28731
|
...endpoint.flow === void 0 ? {} : { protected: true }
|
|
28402
|
-
}).then((joined) => {
|
|
28732
|
+
}, ingress).then((joined) => {
|
|
28403
28733
|
if (!joined && !released) fail(transport.lastJoinOutcome);
|
|
28404
28734
|
});
|
|
28405
28735
|
}
|
|
@@ -31170,12 +31500,12 @@ function moqtPriority2(value) {
|
|
|
31170
31500
|
function catalogRepairTarget(catalog, source) {
|
|
31171
31501
|
const binding = bindingOf(catalog, source);
|
|
31172
31502
|
if (binding?.status !== "ok" || !(binding.base.interleaveDepthMs ?? 0)) return null;
|
|
31173
|
-
const
|
|
31503
|
+
const layerZero2 = binding.layers[0];
|
|
31174
31504
|
const m = /^\/tracks\/(\d+)$/.exec(binding.pointer);
|
|
31175
31505
|
const tracks = catalog.tracks;
|
|
31176
31506
|
const entry = m && Array.isArray(tracks) ? tracks[Number(m[1])] : void 0;
|
|
31177
31507
|
const sourcePriority = entry ? moqtPriority2(entry["priority"]) : null;
|
|
31178
|
-
return { repairTrack:
|
|
31508
|
+
return { repairTrack: layerZero2.repairTrackName, sourcePriority, repairPriority: layerZero2.priority };
|
|
31179
31509
|
}
|
|
31180
31510
|
function numericField(value) {
|
|
31181
31511
|
const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
|
|
@@ -31257,7 +31587,14 @@ function resolveCatalogTrackFec(catalog, tracks) {
|
|
|
31257
31587
|
var PATH_BITS = { moq: 1, multicast: 2 };
|
|
31258
31588
|
var MIXED_PATHS = PATH_BITS.moq | PATH_BITS.multicast;
|
|
31259
31589
|
var MmtpFecSession = class {
|
|
31590
|
+
/** P_0 = B_0, the held total every repair packet is checked against. */
|
|
31591
|
+
get repairSymbols() {
|
|
31592
|
+
return this.#repairSymbols;
|
|
31593
|
+
}
|
|
31260
31594
|
#decoder;
|
|
31595
|
+
#repairSymbols;
|
|
31596
|
+
/** The message entry a catalog-less session was built from; undefined under a catalog. */
|
|
31597
|
+
#message;
|
|
31261
31598
|
/** Blocks a repair has defined and that are not settled yet, by SS_Start. */
|
|
31262
31599
|
#blocks = /* @__PURE__ */ new Map();
|
|
31263
31600
|
/** SS_Starts of settled blocks, so a later repair for one is refused. */
|
|
@@ -31295,6 +31632,24 @@ var MmtpFecSession = class {
|
|
|
31295
31632
|
* `interleaveDepthMs` or `reorderToleranceMs`. None is defaulted.
|
|
31296
31633
|
*/
|
|
31297
31634
|
constructor(config) {
|
|
31635
|
+
if (config && "alFecFlow" in config) {
|
|
31636
|
+
const message = alFecRaptorQFlowConfig(config.alFecFlow);
|
|
31637
|
+
if (!message) {
|
|
31638
|
+
throw new Error("MmtpFecSession: the AL-FEC entry is outside draft-ramadan-moq-mmt-02 8.3 (iso-ssbg1-v1)");
|
|
31639
|
+
}
|
|
31640
|
+
this.#message = message;
|
|
31641
|
+
this.blockProfile = parseFecBlockProfile("iso-ssbg1-v1", "alFecFlow");
|
|
31642
|
+
this.algorithm = message.fecCodeId === FEC_CODE_RAPTORQ ? "raptorq" : "reed-solomon";
|
|
31643
|
+
this.sourceSymbols = message.maximumK;
|
|
31644
|
+
this.#repairSymbols = message.maximumP;
|
|
31645
|
+
this.symbolSize = message.repairSymbolSize;
|
|
31646
|
+
this.retentionBlocks = requirePositiveInteger(config.retentionBlocks, "retentionBlocks");
|
|
31647
|
+
this.#repairPacketId = message.repairFlowId;
|
|
31648
|
+
this.#heldLayers = layerZero(message.maximumP);
|
|
31649
|
+
this.#anchorHorizon = heldLayerAnchorHorizon(this.retentionBlocks, this.sourceSymbols);
|
|
31650
|
+
this.#decoder = buildDecoder(config.Decoder, this);
|
|
31651
|
+
return;
|
|
31652
|
+
}
|
|
31298
31653
|
const fec = config?.fec;
|
|
31299
31654
|
if (!fec || typeof fec !== "object") {
|
|
31300
31655
|
throw new Error("MmtpFecSession: the catalog fec instance is required");
|
|
@@ -31305,7 +31660,7 @@ var MmtpFecSession = class {
|
|
|
31305
31660
|
}
|
|
31306
31661
|
this.algorithm = fec.scheme;
|
|
31307
31662
|
this.sourceSymbols = requirePositiveInteger(fec.sourceSymbols, "fec.sourceSymbols");
|
|
31308
|
-
this
|
|
31663
|
+
this.#repairSymbols = requirePositiveInteger(fec.repairSymbols, "fec.repairSymbols");
|
|
31309
31664
|
this.symbolSize = requirePositiveInteger(fec.symbolSize, "fec.symbolSize");
|
|
31310
31665
|
const interleaveDepthMs = requireNumber(fec.interleaveDepthMs, "fec.interleaveDepthMs");
|
|
31311
31666
|
const reorderToleranceMs = requireNumber(fec.reorderToleranceMs, "fec.reorderToleranceMs");
|
|
@@ -31325,17 +31680,30 @@ var MmtpFecSession = class {
|
|
|
31325
31680
|
config.heldLayers
|
|
31326
31681
|
);
|
|
31327
31682
|
this.#anchorHorizon = heldLayerAnchorHorizon(this.retentionBlocks, this.sourceSymbols);
|
|
31328
|
-
|
|
31329
|
-
|
|
31330
|
-
|
|
31331
|
-
|
|
31332
|
-
|
|
31333
|
-
|
|
31334
|
-
|
|
31335
|
-
|
|
31336
|
-
|
|
31337
|
-
|
|
31338
|
-
|
|
31683
|
+
this.#decoder = buildDecoder(config.Decoder, this);
|
|
31684
|
+
}
|
|
31685
|
+
/**
|
|
31686
|
+
* A later version of the AL-FEC message, catalog-less sessions only. A
|
|
31687
|
+
* maximum_p_for_repair_flow above the held total raises it (`raised`): the
|
|
31688
|
+
* anchored check restarts at the new total, so a block generated under the
|
|
31689
|
+
* previous one stays valid before the first block carrying the new total
|
|
31690
|
+
* (fec-core-00 7.3.2, mmt-02 8.3). A field at or below it changes nothing
|
|
31691
|
+
* (`unchanged`): no publisher lowers it, and the check catches a wire that
|
|
31692
|
+
* does. An entry with another asset, repair flow, code point, K or T, or
|
|
31693
|
+
* one outside 8.3, is `refused`: that is a new source encoding, which needs
|
|
31694
|
+
* a new session. A latched fail-closed stays latched either way.
|
|
31695
|
+
*/
|
|
31696
|
+
updateAlFecFlow(flow) {
|
|
31697
|
+
const held = this.#message;
|
|
31698
|
+
if (!held) throw new Error("MmtpFecSession: updateAlFecFlow() needs a session built from an AL-FEC entry");
|
|
31699
|
+
const next = alFecRaptorQFlowConfig(flow);
|
|
31700
|
+
if (!next || next.sourcePacketIds[0] !== held.sourcePacketIds[0] || next.repairFlowId !== held.repairFlowId || next.fecCodeId !== held.fecCodeId || next.maximumK !== held.maximumK || next.repairSymbolSize !== held.repairSymbolSize) {
|
|
31701
|
+
return "refused";
|
|
31702
|
+
}
|
|
31703
|
+
if (next.maximumP <= this.#repairSymbols) return "unchanged";
|
|
31704
|
+
this.#repairSymbols = next.maximumP;
|
|
31705
|
+
this.#heldLayers = layerZero(next.maximumP);
|
|
31706
|
+
return "raised";
|
|
31339
31707
|
}
|
|
31340
31708
|
/** True once release() ran. */
|
|
31341
31709
|
get released() {
|
|
@@ -31430,7 +31798,7 @@ var MmtpFecSession = class {
|
|
|
31430
31798
|
}
|
|
31431
31799
|
if (admission.reject) {
|
|
31432
31800
|
const verdict2 = this.#heldLayers.failsClosed(
|
|
31433
|
-
repairTrackName,
|
|
31801
|
+
this.#trackFor(repairTrackName),
|
|
31434
31802
|
frame.ssStart,
|
|
31435
31803
|
frame.rsbLength,
|
|
31436
31804
|
this.#anchorHorizon,
|
|
@@ -31445,7 +31813,7 @@ var MmtpFecSession = class {
|
|
|
31445
31813
|
this.#repairPacketId ??= frame.packetId;
|
|
31446
31814
|
if (frame.ssbLength > this.sourceSymbols) return this.#drop("repair:ssb-length");
|
|
31447
31815
|
const verdict = this.#heldLayers.admit(
|
|
31448
|
-
repairTrackName,
|
|
31816
|
+
this.#trackFor(repairTrackName),
|
|
31449
31817
|
frame.ssStart,
|
|
31450
31818
|
frame.rsbLength,
|
|
31451
31819
|
this.#anchorHorizon,
|
|
@@ -31504,6 +31872,10 @@ var MmtpFecSession = class {
|
|
|
31504
31872
|
decoder.free();
|
|
31505
31873
|
return true;
|
|
31506
31874
|
}
|
|
31875
|
+
/** Catalog-less: layer 0 has no own-track clause (mmt-02 8.3), so no track name is judged. */
|
|
31876
|
+
#trackFor(repairTrackName) {
|
|
31877
|
+
return this.#message ? void 0 : repairTrackName;
|
|
31878
|
+
}
|
|
31507
31879
|
#drop(reason) {
|
|
31508
31880
|
this.#stats.dropped[reason] = (this.#stats.dropped[reason] ?? 0) + 1;
|
|
31509
31881
|
return [];
|
|
@@ -31625,6 +31997,22 @@ var MmtpFecSession = class {
|
|
|
31625
31997
|
}
|
|
31626
31998
|
}
|
|
31627
31999
|
};
|
|
32000
|
+
function layerZero(total) {
|
|
32001
|
+
return new HeldLayerAdmission([{ layer: 0, repairTrackName: "", rsIdOffset: 0, repairSymbols: total }]);
|
|
32002
|
+
}
|
|
32003
|
+
function buildDecoder(Decoder, session) {
|
|
32004
|
+
if (typeof Decoder !== "function") {
|
|
32005
|
+
throw new Error("MmtpFecSession: a decoder constructor (wasm_bindgen.MmtFecDecoder) is required");
|
|
32006
|
+
}
|
|
32007
|
+
const decoder = new Decoder(session.retentionBlocks, session.blockProfile, session.algorithm);
|
|
32008
|
+
try {
|
|
32009
|
+
decoder.configure_params(session.sourceSymbols, session.symbolSize);
|
|
32010
|
+
} catch (error) {
|
|
32011
|
+
decoder.free();
|
|
32012
|
+
throw error;
|
|
32013
|
+
}
|
|
32014
|
+
return decoder;
|
|
32015
|
+
}
|
|
31628
32016
|
function requirePositiveInteger(value, path) {
|
|
31629
32017
|
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
|
|
31630
32018
|
throw new Error(`MmtpFecSession: ${path} must be a positive integer (got ${String(value)})`);
|