@blockcast/mmt-container 0.4.1 → 0.5.0-main.2c1f81817995
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 +3458 -356
- 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 +177 -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.map +1 -1
- package/dist/index.js.map +2 -2
- package/dist/mmtp-container-parser.no-modules.js +2682 -1023
- package/dist/mmtp-container-parser.no-modules.js.map +4 -4
- 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 +72 -28
- package/dist/mmtp-router.d.ts.map +1 -1
- package/dist/mmtp-router.js +321 -203
- 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 +48 -9
- package/dist/multicast-manager.d.ts.map +1 -1
- package/dist/multicast-manager.js +3406 -340
- 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 +2668 -1007
- package/dist/no-modules.js.map +4 -4
- package/dist/repair-fetch.d.ts +10 -1
- package/dist/repair-fetch.d.ts.map +1 -1
- package/dist/repair-fetch.js +5 -2
- package/dist/repair-fetch.js.map +2 -2
- package/package.json +5 -14
- package/src/cmcd-producer.ts +6 -1
- package/src/fec-manager.ts +121 -131
- package/src/held-layer-admission.ts +161 -0
- package/src/index.ts +5 -9
- package/src/mmtp-fec-session.ts +76 -3
- package/src/mmtp-router.ts +390 -272
- package/src/multicast-auth-catalog.ts +81 -8
- package/src/multicast-manager.ts +189 -48
- package/src/multicast-transport.ts +6 -2
- package/src/no-modules.ts +1 -6
- package/src/repair-fetch.ts +24 -4
- 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
package/src/repair-fetch.ts
CHANGED
|
@@ -394,6 +394,13 @@ export type RepairSkipReason =
|
|
|
394
394
|
| 'presentation-delay-unavailable'
|
|
395
395
|
/** A lost SS_ID in an undelimited span had no §7.5 anchor to extend it from. */
|
|
396
396
|
| 'range-no-anchor'
|
|
397
|
+
/**
|
|
398
|
+
* The block lost more sources than its repair group carries symbols
|
|
399
|
+
* (sources + RSB_length < K'): no FETCH of the group can complete it, and
|
|
400
|
+
* when multicast already delivered the whole group the relay could serve
|
|
401
|
+
* only duplicates (BLO-50082).
|
|
402
|
+
*/
|
|
403
|
+
| 'group-cannot-complete'
|
|
397
404
|
|
|
398
405
|
/**
|
|
399
406
|
* Which term set a FETCH's deadline (BLO-33597): the multicast wait, the
|
|
@@ -477,6 +484,8 @@ export interface RepairFetchHost {
|
|
|
477
484
|
closeIsoHoles?(packetId: number, catalogTrackName: string | undefined, start: number, length: number): void
|
|
478
485
|
clearRepairForBlock(sbn: number, packetId: number, catalogTrackName?: string): void
|
|
479
486
|
processRepairFromMoQ(packet: Uint8Array, ownerPacketId?: number, catalogTrackName?: string): void
|
|
487
|
+
/** This scheduler can request unicast repair: protected flows keep pending entries for it (optional). */
|
|
488
|
+
setUnicastRepair?(enabled: boolean): void
|
|
480
489
|
noteRepairScheduling(
|
|
481
490
|
type: 'repair-preempted' | 'repair-deferred',
|
|
482
491
|
sbn: number,
|
|
@@ -592,6 +601,7 @@ export function wireRepairFetch(mc: RepairFetchHost, opts: WireRepairFetchOption
|
|
|
592
601
|
) {
|
|
593
602
|
throw new Error('[RepairFetch] rangeTransport needs repairEpochReference and a host that reports undelimited losses')
|
|
594
603
|
}
|
|
604
|
+
mc.setUnicastRepair?.(true)
|
|
595
605
|
const entries = new Map<string, RepairEntry>()
|
|
596
606
|
const warnedNoTarget = new Set<string>()
|
|
597
607
|
let disposed = false
|
|
@@ -656,19 +666,28 @@ export function wireRepairFetch(mc: RepairFetchHost, opts: WireRepairFetchOption
|
|
|
656
666
|
}
|
|
657
667
|
|
|
658
668
|
/**
|
|
659
|
-
*
|
|
660
|
-
*
|
|
669
|
+
* Admission: send only a FETCH whose repair group can complete the block
|
|
670
|
+
* and that the measured round trip says can land before the entry's
|
|
671
|
+
* deadline. A refused entry is released, not queued: the
|
|
661
672
|
* deadline only gets closer. Returns the admitting RTT, or null when refused.
|
|
662
673
|
*/
|
|
663
674
|
const admit = (entry: RepairEntry, now: number): number | null => {
|
|
664
675
|
const budgetMs = entry.deadlineAt - now
|
|
665
676
|
const rttMs = readRtt()
|
|
666
|
-
|
|
677
|
+
const reason: RepairSkipReason | null =
|
|
678
|
+
!entry.range && !groupCanComplete(entry.sbn, entry.packetId, entry.catalogTrackName)
|
|
679
|
+
? 'group-cannot-complete'
|
|
680
|
+
: rttMs === null
|
|
681
|
+
? 'rtt-unmeasured'
|
|
682
|
+
: rttMs < budgetMs
|
|
683
|
+
? null
|
|
684
|
+
: 'rtt-exceeds-budget'
|
|
685
|
+
if (reason === null) return rttMs
|
|
667
686
|
retire(entry)
|
|
668
687
|
releaseHold(entry)
|
|
669
688
|
clearEntry(entry)
|
|
670
689
|
mc.noteRepairSkipped(
|
|
671
|
-
|
|
690
|
+
reason,
|
|
672
691
|
{ rttMs, budgetMs, priority: entry.priority },
|
|
673
692
|
entry.sbn,
|
|
674
693
|
entry.packetId,
|
|
@@ -1233,6 +1252,7 @@ export function wireRepairFetch(mc: RepairFetchHost, opts: WireRepairFetchOption
|
|
|
1233
1252
|
|
|
1234
1253
|
return () => {
|
|
1235
1254
|
disposed = true
|
|
1255
|
+
mc.setUnicastRepair?.(false)
|
|
1236
1256
|
removeResetObserver()
|
|
1237
1257
|
removeLifecycleObserver()
|
|
1238
1258
|
for (const entry of [...entries.values()]) {
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { ManifestKey } from '@blockcast/mmt-manifest-verify';
|
|
2
|
-
import { type ResolvedMulticastAuthFlow } from './multicast-auth-catalog.js';
|
|
3
|
-
/**
|
|
4
|
-
* The bc-provenance version 2 plan a receiver binds from one catalog
|
|
5
|
-
* (draft-ramadan-moq-multicast-01 7.2.6): the key set, `minEpoch`, and one
|
|
6
|
-
* flow per protected endpoint. The record verifier and the 7.2.7 state machine
|
|
7
|
-
* consume it; this plan holds no verification state.
|
|
8
|
-
*/
|
|
9
|
-
export interface ManifestAuthPlan {
|
|
10
|
-
keys: readonly ManifestKey[];
|
|
11
|
-
minEpoch: number;
|
|
12
|
-
flows: readonly ResolvedMulticastAuthFlow[];
|
|
13
|
-
/** Every `manifestTrack` a flow names, for a unicast receiver to subscribe. */
|
|
14
|
-
manifestTracks: readonly string[];
|
|
15
|
-
/** The key that signs a record of `kid` at `epoch`; `undefined` drops it.
|
|
16
|
-
* Key selection only: it does not apply `minEpoch`, which the record
|
|
17
|
-
* verifier checks (BLO-49771). */
|
|
18
|
-
keyFor(kid: number, epoch: number): Uint8Array | undefined;
|
|
19
|
-
/** The flow whose records arrive on `(S, G, manifestPort)`. */
|
|
20
|
-
flowForManifestPort(source: string, groupAddress: string, manifestPort: number): ResolvedMulticastAuthFlow | undefined;
|
|
21
|
-
/** The flow a manifest track mirrors. */
|
|
22
|
-
flowForManifestTrack(manifestTrack: string): ResolvedMulticastAuthFlow | undefined;
|
|
23
|
-
}
|
|
24
|
-
/** Build the plan, or `undefined` for an unprotected catalog. Throws where
|
|
25
|
-
* `resolveMulticastAuthCatalog` does: no key set, or authentication pinned
|
|
26
|
-
* by `requireAuth` on a catalog without `multicast.auth`. */
|
|
27
|
-
export declare function createManifestAuthPlan(catalog: unknown, options?: {
|
|
28
|
-
requireAuth?: boolean;
|
|
29
|
-
catalogNamespace?: string;
|
|
30
|
-
}): ManifestAuthPlan | undefined;
|
|
31
|
-
//# sourceMappingURL=manifest-auth-plan.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"manifest-auth-plan.d.ts","sourceRoot":"","sources":["../src/manifest-auth-plan.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAA;AACjE,OAAO,EAIL,KAAK,yBAAyB,EAC/B,MAAM,6BAA6B,CAAA;AAEpC;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,SAAS,WAAW,EAAE,CAAA;IAC5B,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,SAAS,yBAAyB,EAAE,CAAA;IAC3C,+EAA+E;IAC/E,cAAc,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC;;sCAEkC;IAClC,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAAA;IAC1D,+DAA+D;IAC/D,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,yBAAyB,GAAG,SAAS,CAAA;IACtH,yCAAyC;IACzC,oBAAoB,CAAC,aAAa,EAAE,MAAM,GAAG,yBAAyB,GAAG,SAAS,CAAA;CACnF;AAED;;6DAE6D;AAC7D,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAO,GACjE,gBAAgB,GAAG,SAAS,CAkB9B"}
|
|
@@ -1,426 +0,0 @@
|
|
|
1
|
-
// ../mmt-base/dist/mmtp.js
|
|
2
|
-
var MPU_HEADER_SIZE = 8;
|
|
3
|
-
var MPU_LENGTH_FIELD_SIZE = 2;
|
|
4
|
-
var MPU_HEADER_REMAINDER_SIZE = MPU_HEADER_SIZE - MPU_LENGTH_FIELD_SIZE;
|
|
5
|
-
|
|
6
|
-
// ../mmt-base/dist/ntp-short.js
|
|
7
|
-
var NTP_SHORT_BITS = 32;
|
|
8
|
-
var NTP_SHORT_MODULUS = 2 ** NTP_SHORT_BITS;
|
|
9
|
-
var NTP_SHORT_HALF_RANGE = 2 ** (NTP_SHORT_BITS - 1);
|
|
10
|
-
|
|
11
|
-
// ../mmt-base/dist/fec-block-profile.js
|
|
12
|
-
var FEC_BLOCK_PROFILES = Object.freeze([
|
|
13
|
-
"iso-ssbg1-v1"
|
|
14
|
-
]);
|
|
15
|
-
|
|
16
|
-
// ../mmt-base/dist/catalog-fec-source.js
|
|
17
|
-
var ALL_ROLES = Object.freeze(["video", "audio"]);
|
|
18
|
-
|
|
19
|
-
// ../mmt-base/dist/multicast-catalog.js
|
|
20
|
-
var ED25519_PUBLIC_KEY_BASE64URL = /^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$/;
|
|
21
|
-
var UINT32_MAX = 4294967295;
|
|
22
|
-
var AUTH_MEMBERS = /* @__PURE__ */ new Set(["scheme", "version", "sig", "keys", "minEpoch"]);
|
|
23
|
-
var KEY_MEMBERS = /* @__PURE__ */ new Set(["kid", "publicKey", "firstEpoch"]);
|
|
24
|
-
var ENDPOINT_AUTH_MEMBERS = /* @__PURE__ */ new Set([
|
|
25
|
-
"manifestPort",
|
|
26
|
-
"streamId",
|
|
27
|
-
"senderAddress",
|
|
28
|
-
"maxAuthDelayMs",
|
|
29
|
-
"manifestTimerMs",
|
|
30
|
-
"manifestTrack"
|
|
31
|
-
]);
|
|
32
|
-
function isObject(value) {
|
|
33
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
34
|
-
}
|
|
35
|
-
function isNonEmptyString(value) {
|
|
36
|
-
return typeof value === "string" && value.trim().length > 0;
|
|
37
|
-
}
|
|
38
|
-
function isIntegerIn(value, min, max) {
|
|
39
|
-
return Number.isSafeInteger(value) && value >= min && value <= max;
|
|
40
|
-
}
|
|
41
|
-
function multicastEndpointSource(endpoint) {
|
|
42
|
-
if (!isObject(endpoint))
|
|
43
|
-
return void 0;
|
|
44
|
-
const sourceAddress = endpoint["sourceAddress"];
|
|
45
|
-
if (sourceAddress !== void 0 && !isNonEmptyString(sourceAddress))
|
|
46
|
-
return void 0;
|
|
47
|
-
const mode = sourceAddress === void 0 ? "asm" : "ssm";
|
|
48
|
-
const protocol = endpoint["protocol"];
|
|
49
|
-
if (protocol !== void 0 && protocol !== mode)
|
|
50
|
-
return void 0;
|
|
51
|
-
const auth = endpoint["auth"];
|
|
52
|
-
const senderAddress = isObject(auth) ? auth["senderAddress"] : void 0;
|
|
53
|
-
if (mode === "ssm")
|
|
54
|
-
return senderAddress === void 0 ? { mode, source: sourceAddress } : void 0;
|
|
55
|
-
if (auth === void 0)
|
|
56
|
-
return { mode };
|
|
57
|
-
return isNonEmptyString(senderAddress) ? { mode, source: senderAddress } : void 0;
|
|
58
|
-
}
|
|
59
|
-
function authIssues(auth) {
|
|
60
|
-
const base = "$.multicast.auth";
|
|
61
|
-
const issue = (path, message) => [
|
|
62
|
-
{ code: "multicast_auth", path: `${base}${path}`, message }
|
|
63
|
-
];
|
|
64
|
-
if (!isObject(auth))
|
|
65
|
-
return issue("", "auth must be an object");
|
|
66
|
-
const unknown = Object.keys(auth).filter((key) => !AUTH_MEMBERS.has(key));
|
|
67
|
-
if (unknown.length > 0) {
|
|
68
|
-
return issue("", `auth has unknown member(s) ${unknown.join(", ")}; draft -01 7.2.6 defines scheme, version, sig, keys and minEpoch`);
|
|
69
|
-
}
|
|
70
|
-
if (auth["scheme"] !== "bc-provenance")
|
|
71
|
-
return issue(".scheme", 'scheme must be "bc-provenance"');
|
|
72
|
-
if (auth["version"] !== 2)
|
|
73
|
-
return issue(".version", "version must be 2 (bc-provenance version 2, draft -01 7.2)");
|
|
74
|
-
if (auth["sig"] !== "ed25519")
|
|
75
|
-
return issue(".sig", 'sig must be "ed25519"');
|
|
76
|
-
const keys = auth["keys"];
|
|
77
|
-
if (!Array.isArray(keys) || keys.length < 1 || keys.length > 4) {
|
|
78
|
-
return issue(".keys", "keys must be an array of one to four {kid, publicKey, firstEpoch} objects");
|
|
79
|
-
}
|
|
80
|
-
const kids = /* @__PURE__ */ new Set();
|
|
81
|
-
const firstEpochs = /* @__PURE__ */ new Set();
|
|
82
|
-
for (let index = 0; index < keys.length; index++) {
|
|
83
|
-
const key = keys[index];
|
|
84
|
-
const at = `.keys[${index}]`;
|
|
85
|
-
if (!isObject(key) || Object.keys(key).some((member) => !KEY_MEMBERS.has(member))) {
|
|
86
|
-
return issue(at, "a key is exactly {kid, publicKey, firstEpoch}");
|
|
87
|
-
}
|
|
88
|
-
if (!isIntegerIn(key["kid"], 0, UINT32_MAX))
|
|
89
|
-
return issue(`${at}.kid`, "kid must be an unsigned 32-bit integer");
|
|
90
|
-
if (typeof key["publicKey"] !== "string" || !ED25519_PUBLIC_KEY_BASE64URL.test(key["publicKey"])) {
|
|
91
|
-
return issue(`${at}.publicKey`, "publicKey must be a 32-octet Ed25519 key in canonical unpadded base64url");
|
|
92
|
-
}
|
|
93
|
-
if (!isIntegerIn(key["firstEpoch"], 0, UINT32_MAX)) {
|
|
94
|
-
return issue(`${at}.firstEpoch`, "firstEpoch must be an unsigned 32-bit integer");
|
|
95
|
-
}
|
|
96
|
-
if (kids.has(key["kid"]))
|
|
97
|
-
return issue(`${at}.kid`, `kid ${key["kid"]} is repeated; the catalog holds no key set`);
|
|
98
|
-
if (firstEpochs.has(key["firstEpoch"])) {
|
|
99
|
-
return issue(`${at}.firstEpoch`, `firstEpoch ${key["firstEpoch"]} is repeated; the catalog holds no key set`);
|
|
100
|
-
}
|
|
101
|
-
kids.add(key["kid"]);
|
|
102
|
-
firstEpochs.add(key["firstEpoch"]);
|
|
103
|
-
}
|
|
104
|
-
if (!isIntegerIn(auth["minEpoch"], 0, UINT32_MAX)) {
|
|
105
|
-
return issue(".minEpoch", "minEpoch must be an unsigned 32-bit integer");
|
|
106
|
-
}
|
|
107
|
-
return [];
|
|
108
|
-
}
|
|
109
|
-
function endpointAuthIssue(endpoint, mode, at) {
|
|
110
|
-
const auth = endpoint["auth"];
|
|
111
|
-
if (!isObject(auth))
|
|
112
|
-
return { path: `${at}.auth`, message: "auth must be an object" };
|
|
113
|
-
const unknown = Object.keys(auth).filter((key) => !ENDPOINT_AUTH_MEMBERS.has(key));
|
|
114
|
-
if (unknown.length > 0)
|
|
115
|
-
return { path: `${at}.auth`, message: `auth has unknown member(s) ${unknown.join(", ")}` };
|
|
116
|
-
if (!isIntegerIn(auth["manifestPort"], 1, 65535)) {
|
|
117
|
-
return { path: `${at}.auth.manifestPort`, message: "manifestPort must be an integer from 1 to 65535" };
|
|
118
|
-
}
|
|
119
|
-
if (!isIntegerIn(auth["streamId"], 0, UINT32_MAX)) {
|
|
120
|
-
return { path: `${at}.auth.streamId`, message: "streamId must be an unsigned 32-bit integer" };
|
|
121
|
-
}
|
|
122
|
-
if (mode === "ssm" && auth["senderAddress"] !== void 0) {
|
|
123
|
-
return { path: `${at}.auth.senderAddress`, message: "senderAddress must not appear on an SSM endpoint; S is sourceAddress" };
|
|
124
|
-
}
|
|
125
|
-
if (mode === "asm" && !isNonEmptyString(auth["senderAddress"])) {
|
|
126
|
-
return { path: `${at}.auth.senderAddress`, message: "a protected ASM endpoint requires senderAddress" };
|
|
127
|
-
}
|
|
128
|
-
if (auth["maxAuthDelayMs"] !== void 0 && !isIntegerIn(auth["maxAuthDelayMs"], 50, 1e3)) {
|
|
129
|
-
return { path: `${at}.auth.maxAuthDelayMs`, message: "maxAuthDelayMs must be an integer from 50 to 1000" };
|
|
130
|
-
}
|
|
131
|
-
if (auth["manifestTimerMs"] !== void 0 && !isIntegerIn(auth["manifestTimerMs"], 1, Number.MAX_SAFE_INTEGER)) {
|
|
132
|
-
return { path: `${at}.auth.manifestTimerMs`, message: "manifestTimerMs must be an integer of at least 1" };
|
|
133
|
-
}
|
|
134
|
-
if (auth["manifestTrack"] !== void 0 && !isNonEmptyString(auth["manifestTrack"])) {
|
|
135
|
-
return { path: `${at}.auth.manifestTrack`, message: "manifestTrack must be a non-empty string" };
|
|
136
|
-
}
|
|
137
|
-
if (!isIntegerIn(endpoint["bandwidth"], 1, Number.MAX_SAFE_INTEGER)) {
|
|
138
|
-
return { path: `${at}.bandwidth`, message: "a protected endpoint requires bandwidth, which bounds the receiver's hold queues" };
|
|
139
|
-
}
|
|
140
|
-
return void 0;
|
|
141
|
-
}
|
|
142
|
-
var CATALOG_NAMESPACE = /* @__PURE__ */ Symbol.for("@blockcast/mmt-base/catalogNamespace");
|
|
143
|
-
function multicastCatalogIssues(catalog, options = {}) {
|
|
144
|
-
if (!isObject(catalog))
|
|
145
|
-
return [];
|
|
146
|
-
const issues = [];
|
|
147
|
-
if ("multicast.auth" in catalog) {
|
|
148
|
-
issues.push({
|
|
149
|
-
code: "multicast_auth",
|
|
150
|
-
path: '$["multicast.auth"]',
|
|
151
|
-
message: 'the root "multicast.auth" key is retired; declare auth as multicast.auth (draft -01 7.2.6)'
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
const multicast = catalog["multicast"];
|
|
155
|
-
if (!isObject(multicast))
|
|
156
|
-
return issues;
|
|
157
|
-
const protectedCatalog = multicast["auth"] !== void 0;
|
|
158
|
-
if (protectedCatalog)
|
|
159
|
-
issues.push(...authIssues(multicast["auth"]));
|
|
160
|
-
const endpoints = multicast["endpoints"];
|
|
161
|
-
if (!Array.isArray(endpoints))
|
|
162
|
-
return issues;
|
|
163
|
-
const tracks = Array.isArray(catalog["tracks"]) ? catalog["tracks"] : [];
|
|
164
|
-
const namespaceOf = /* @__PURE__ */ new Map();
|
|
165
|
-
for (const track of tracks) {
|
|
166
|
-
if (isObject(track) && typeof track["name"] === "string")
|
|
167
|
-
namespaceOf.set(track["name"], track["namespace"]);
|
|
168
|
-
}
|
|
169
|
-
const tagged = catalog[CATALOG_NAMESPACE];
|
|
170
|
-
let referenceNamespace = options.catalogNamespace ?? (typeof tagged === "string" ? tagged : void 0);
|
|
171
|
-
const sources = [];
|
|
172
|
-
for (let index = 0; index < endpoints.length; index++) {
|
|
173
|
-
const endpoint = endpoints[index];
|
|
174
|
-
const at = `$.multicast.endpoints[${index}]`;
|
|
175
|
-
const fail = (code, path, message) => {
|
|
176
|
-
issues.push({ code, path, message, endpointIndex: index });
|
|
177
|
-
};
|
|
178
|
-
sources.push(void 0);
|
|
179
|
-
if (!isObject(endpoint)) {
|
|
180
|
-
fail("multicast_endpoint", at, "endpoint must be an object");
|
|
181
|
-
continue;
|
|
182
|
-
}
|
|
183
|
-
const protocol = endpoint["protocol"];
|
|
184
|
-
if (protocol !== void 0 && protocol !== "ssm" && protocol !== "asm") {
|
|
185
|
-
fail("multicast_endpoint", `${at}.protocol`, `protocol must be "ssm" or "asm"; a bearer such as ${JSON.stringify(protocol)} is a networkSource entry (draft -01 4.2)`);
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
const hasAuth = endpoint["auth"] !== void 0;
|
|
189
|
-
if (protectedCatalog !== hasAuth) {
|
|
190
|
-
fail("multicast_endpoint_auth", `${at}.auth`, protectedCatalog ? "every endpoint requires auth when multicast carries auth; a receiver ignores this endpoint" : "auth names no key without multicast.auth; a receiver ignores this endpoint");
|
|
191
|
-
continue;
|
|
192
|
-
}
|
|
193
|
-
const sourceAddress = endpoint["sourceAddress"];
|
|
194
|
-
if (sourceAddress !== void 0 && !isNonEmptyString(sourceAddress)) {
|
|
195
|
-
fail("multicast_endpoint", `${at}.sourceAddress`, "sourceAddress must be a non-empty string");
|
|
196
|
-
continue;
|
|
197
|
-
}
|
|
198
|
-
const mode = sourceAddress === void 0 ? "asm" : "ssm";
|
|
199
|
-
if (protocol !== void 0 && protocol !== mode) {
|
|
200
|
-
fail("multicast_endpoint", `${at}.protocol`, `protocol ${JSON.stringify(protocol)} disagrees with sourceAddress, which selects ${mode}`);
|
|
201
|
-
continue;
|
|
202
|
-
}
|
|
203
|
-
if (!isIntegerIn(endpoint["port"], 1, 65535)) {
|
|
204
|
-
fail("multicast_endpoint", `${at}.port`, "port must be an integer from 1 to 65535");
|
|
205
|
-
continue;
|
|
206
|
-
}
|
|
207
|
-
if (hasAuth) {
|
|
208
|
-
const authIssue = endpointAuthIssue(endpoint, mode, at);
|
|
209
|
-
if (authIssue) {
|
|
210
|
-
fail("multicast_endpoint_auth", authIssue.path, authIssue.message);
|
|
211
|
-
continue;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
sources[index] = multicastEndpointSource(endpoint);
|
|
215
|
-
const refs = Array.isArray(endpoint["tracks"]) ? endpoint["tracks"] : [];
|
|
216
|
-
for (let refIndex = 0; refIndex < refs.length; refIndex++) {
|
|
217
|
-
const ref = refs[refIndex];
|
|
218
|
-
const name = isObject(ref) ? ref["name"] : ref;
|
|
219
|
-
if (typeof name !== "string")
|
|
220
|
-
continue;
|
|
221
|
-
const namespace = namespaceOf.get(name);
|
|
222
|
-
if (typeof namespace !== "string")
|
|
223
|
-
continue;
|
|
224
|
-
referenceNamespace ??= namespace;
|
|
225
|
-
if (namespace !== referenceNamespace) {
|
|
226
|
-
fail("multicast_track", `${at}.tracks[${refIndex}].name`, `track ${JSON.stringify(name)} declares namespace ${JSON.stringify(namespace)}, not the catalog track's ${JSON.stringify(referenceNamespace)}`);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
const tupleOf = (index) => {
|
|
231
|
-
const endpoint = endpoints[index];
|
|
232
|
-
const auth = endpoint["auth"];
|
|
233
|
-
return {
|
|
234
|
-
sg: JSON.stringify([sources[index].source ?? null, endpoint["groupAddress"]]),
|
|
235
|
-
port: endpoint["port"],
|
|
236
|
-
manifestPort: isObject(auth) ? auth["manifestPort"] : void 0
|
|
237
|
-
};
|
|
238
|
-
};
|
|
239
|
-
for (let index = 0; index < endpoints.length; index++) {
|
|
240
|
-
if (!sources[index])
|
|
241
|
-
continue;
|
|
242
|
-
const own = tupleOf(index);
|
|
243
|
-
const at = `$.multicast.endpoints[${index}]`;
|
|
244
|
-
if (own.manifestPort !== void 0 && own.manifestPort === own.port) {
|
|
245
|
-
issues.push({ code: "multicast_endpoint_auth", path: `${at}.auth.manifestPort`, message: "manifestPort must differ from the endpoint's own port", endpointIndex: index });
|
|
246
|
-
continue;
|
|
247
|
-
}
|
|
248
|
-
for (let other = 0; other < endpoints.length; other++) {
|
|
249
|
-
if (other === index || !sources[other])
|
|
250
|
-
continue;
|
|
251
|
-
const peer = tupleOf(other);
|
|
252
|
-
if (peer.sg !== own.sg)
|
|
253
|
-
continue;
|
|
254
|
-
if (peer.port === own.port) {
|
|
255
|
-
issues.push({ code: "multicast_endpoint", path: `${at}.port`, message: `(S, G, port) is shared with endpoint ${other}; one endpoint object per multicast tuple`, endpointIndex: index });
|
|
256
|
-
break;
|
|
257
|
-
}
|
|
258
|
-
if (own.manifestPort !== void 0 && (own.manifestPort === peer.port || own.manifestPort === peer.manifestPort)) {
|
|
259
|
-
issues.push({ code: "multicast_endpoint_auth", path: `${at}.auth.manifestPort`, message: `manifestPort is a port or manifestPort of endpoint ${other} on the same (S, G)`, endpointIndex: index });
|
|
260
|
-
break;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
return issues;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// ../mmt-manifest-verify/dist/source-auth.js
|
|
268
|
-
var SOURCE_AUTH_MAX_TRUE_LENGTH = 16 * 1024 * 1024;
|
|
269
|
-
var SIGNATURE_DOMAIN = new TextEncoder().encode("blockcast-mmt-root-v1\0\0\0");
|
|
270
|
-
|
|
271
|
-
// ../mmt-manifest-verify/dist/verifier.js
|
|
272
|
-
function manifestKeyFor(keys, epoch, kid) {
|
|
273
|
-
if (new Set(keys.map((key) => key.kid)).size !== keys.length)
|
|
274
|
-
return void 0;
|
|
275
|
-
if (new Set(keys.map((key) => key.firstEpoch)).size !== keys.length)
|
|
276
|
-
return void 0;
|
|
277
|
-
let signer;
|
|
278
|
-
for (const key of keys) {
|
|
279
|
-
if (key.firstEpoch <= epoch && (signer === void 0 || key.firstEpoch > signer.firstEpoch))
|
|
280
|
-
signer = key;
|
|
281
|
-
}
|
|
282
|
-
if (signer === void 0 || kid !== void 0 && signer.kid !== kid)
|
|
283
|
-
return void 0;
|
|
284
|
-
return signer.publicKey;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// ../mmt-manifest-verify/dist/bc-provenance-v2.js
|
|
288
|
-
var BCPV2_HEADER_LENGTH = 68;
|
|
289
|
-
var BCPV2_SIGNATURE_LENGTH = 64;
|
|
290
|
-
var BCPV2_MIN_RECORD_LENGTH = BCPV2_HEADER_LENGTH + 32 + BCPV2_SIGNATURE_LENGTH;
|
|
291
|
-
var text = new TextEncoder();
|
|
292
|
-
var MAGIC = Uint8Array.from([77, 72, 77, 50]);
|
|
293
|
-
var SIGNING_DOMAIN = text.encode("mcast-manifest-v2\0");
|
|
294
|
-
|
|
295
|
-
// src/multicast-auth-catalog.ts
|
|
296
|
-
var RAW_ED25519_KEY_LENGTH = 32;
|
|
297
|
-
var DEFAULT_MAX_AUTH_DELAY_MS = 200;
|
|
298
|
-
var DEFAULT_MANIFEST_TIMER_MS = 20;
|
|
299
|
-
var BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
300
|
-
function encodeBase64Url(bytes) {
|
|
301
|
-
let output = "";
|
|
302
|
-
for (let i = 0; i < bytes.length; i += 3) {
|
|
303
|
-
const b0 = bytes[i];
|
|
304
|
-
const b1 = bytes[i + 1];
|
|
305
|
-
const b2 = bytes[i + 2];
|
|
306
|
-
output += BASE64URL_ALPHABET[b0 >> 2];
|
|
307
|
-
output += BASE64URL_ALPHABET[(b0 & 3) << 4 | (b1 === void 0 ? 0 : b1 >> 4)];
|
|
308
|
-
if (b1 === void 0) break;
|
|
309
|
-
output += BASE64URL_ALPHABET[(b1 & 15) << 2 | (b2 === void 0 ? 0 : b2 >> 6)];
|
|
310
|
-
if (b2 === void 0) break;
|
|
311
|
-
output += BASE64URL_ALPHABET[b2 & 63];
|
|
312
|
-
}
|
|
313
|
-
return output;
|
|
314
|
-
}
|
|
315
|
-
var BASE64URL_DECODE_TABLE = (() => {
|
|
316
|
-
const table = new Int8Array(128).fill(-1);
|
|
317
|
-
for (let i = 0; i < BASE64URL_ALPHABET.length; i++) {
|
|
318
|
-
table[BASE64URL_ALPHABET.charCodeAt(i)] = i;
|
|
319
|
-
}
|
|
320
|
-
return table;
|
|
321
|
-
})();
|
|
322
|
-
function decodeBase64Url(text2) {
|
|
323
|
-
if (text2.length % 4 === 1) {
|
|
324
|
-
throw new Error(`[multicast-auth-catalog] invalid unpadded base64url length for "${text2}"`);
|
|
325
|
-
}
|
|
326
|
-
const bytes = [];
|
|
327
|
-
let buffer = 0;
|
|
328
|
-
let bits = 0;
|
|
329
|
-
for (let i = 0; i < text2.length; i++) {
|
|
330
|
-
const code = text2.charCodeAt(i);
|
|
331
|
-
const value = code < 128 ? BASE64URL_DECODE_TABLE[code] : -1;
|
|
332
|
-
if (value === void 0 || value < 0) {
|
|
333
|
-
throw new Error(`[multicast-auth-catalog] invalid base64url character in "${text2}"`);
|
|
334
|
-
}
|
|
335
|
-
buffer = buffer << 6 | value;
|
|
336
|
-
bits += 6;
|
|
337
|
-
if (bits >= 8) {
|
|
338
|
-
bits -= 8;
|
|
339
|
-
bytes.push(buffer >> bits & 255);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
const decoded = Uint8Array.from(bytes);
|
|
343
|
-
if (encodeBase64Url(decoded) !== text2) {
|
|
344
|
-
throw new Error(`[multicast-auth-catalog] non-canonical base64url encoding "${text2}"`);
|
|
345
|
-
}
|
|
346
|
-
return decoded;
|
|
347
|
-
}
|
|
348
|
-
function decodeEd25519PublicKey(publicKey) {
|
|
349
|
-
const bytes = decodeBase64Url(publicKey);
|
|
350
|
-
if (bytes.byteLength !== RAW_ED25519_KEY_LENGTH) {
|
|
351
|
-
throw new Error(
|
|
352
|
-
`[multicast-auth-catalog] public key must decode to ${RAW_ED25519_KEY_LENGTH} raw bytes, got ${bytes.byteLength}`
|
|
353
|
-
);
|
|
354
|
-
}
|
|
355
|
-
return bytes;
|
|
356
|
-
}
|
|
357
|
-
function resolveMulticastAuthCatalog(catalog, options = {}) {
|
|
358
|
-
if (typeof catalog !== "object" || catalog === null || Array.isArray(catalog)) {
|
|
359
|
-
throw new Error("[multicast-auth-catalog] catalog must be an object");
|
|
360
|
-
}
|
|
361
|
-
const issues = multicastCatalogIssues(catalog, options);
|
|
362
|
-
const fatal = issues.find((issue) => issue.endpointIndex === void 0);
|
|
363
|
-
if (fatal) throw new Error(`[multicast-auth-catalog] ${fatal.path}: ${fatal.message}`);
|
|
364
|
-
const multicast = catalog["multicast"];
|
|
365
|
-
const auth = multicast?.["auth"];
|
|
366
|
-
if (auth === void 0) {
|
|
367
|
-
if (options.requireAuth) {
|
|
368
|
-
throw new Error("[multicast-auth-catalog] authentication is pinned but the catalog declares no multicast.auth");
|
|
369
|
-
}
|
|
370
|
-
return void 0;
|
|
371
|
-
}
|
|
372
|
-
const ignored = new Set(issues.map((issue) => issue.endpointIndex));
|
|
373
|
-
const endpoints = multicast["endpoints"] ?? [];
|
|
374
|
-
const flows = [];
|
|
375
|
-
endpoints.forEach((endpoint, endpointIndex) => {
|
|
376
|
-
if (ignored.has(endpointIndex)) return;
|
|
377
|
-
const flowAuth = endpoint["auth"];
|
|
378
|
-
flows.push({
|
|
379
|
-
endpointIndex,
|
|
380
|
-
source: endpoint["sourceAddress"] ?? flowAuth.senderAddress,
|
|
381
|
-
groupAddress: endpoint["groupAddress"],
|
|
382
|
-
port: endpoint["port"],
|
|
383
|
-
bandwidth: endpoint["bandwidth"],
|
|
384
|
-
manifestPort: flowAuth.manifestPort,
|
|
385
|
-
streamId: flowAuth.streamId,
|
|
386
|
-
maxAuthDelayMs: flowAuth.maxAuthDelayMs ?? DEFAULT_MAX_AUTH_DELAY_MS,
|
|
387
|
-
manifestTimerMs: flowAuth.manifestTimerMs ?? DEFAULT_MANIFEST_TIMER_MS,
|
|
388
|
-
...flowAuth.manifestTrack === void 0 ? {} : { manifestTrack: flowAuth.manifestTrack },
|
|
389
|
-
tracks: (endpoint["tracks"] ?? []).map(({ name, packetId }) => ({
|
|
390
|
-
name,
|
|
391
|
-
packetId
|
|
392
|
-
}))
|
|
393
|
-
});
|
|
394
|
-
});
|
|
395
|
-
return {
|
|
396
|
-
keys: auth.keys.map((key) => ({ kid: key.kid, publicKey: decodeEd25519PublicKey(key.publicKey), firstEpoch: key.firstEpoch })).sort((a, b) => a.firstEpoch - b.firstEpoch),
|
|
397
|
-
minEpoch: auth.minEpoch,
|
|
398
|
-
flows
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
function multicastAuthKeyFor(resolved, kid, epoch) {
|
|
402
|
-
return manifestKeyFor(resolved.keys, epoch, kid);
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// src/manifest-auth-plan.ts
|
|
406
|
-
function createManifestAuthPlan(catalog, options = {}) {
|
|
407
|
-
const resolved = resolveMulticastAuthCatalog(catalog, options);
|
|
408
|
-
if (!resolved) return void 0;
|
|
409
|
-
const byManifestTrack = /* @__PURE__ */ new Map();
|
|
410
|
-
for (const flow of resolved.flows) {
|
|
411
|
-
if (flow.manifestTrack !== void 0) byManifestTrack.set(flow.manifestTrack, flow);
|
|
412
|
-
}
|
|
413
|
-
return {
|
|
414
|
-
keys: resolved.keys,
|
|
415
|
-
minEpoch: resolved.minEpoch,
|
|
416
|
-
flows: resolved.flows,
|
|
417
|
-
manifestTracks: [...byManifestTrack.keys()],
|
|
418
|
-
keyFor: (kid, epoch) => multicastAuthKeyFor(resolved, kid, epoch),
|
|
419
|
-
flowForManifestPort: (source, groupAddress, manifestPort) => resolved.flows.find((flow) => flow.source === source && flow.groupAddress === groupAddress && flow.manifestPort === manifestPort),
|
|
420
|
-
flowForManifestTrack: (manifestTrack) => byManifestTrack.get(manifestTrack)
|
|
421
|
-
};
|
|
422
|
-
}
|
|
423
|
-
export {
|
|
424
|
-
createManifestAuthPlan
|
|
425
|
-
};
|
|
426
|
-
//# sourceMappingURL=manifest-auth-plan.js.map
|