@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.
Files changed (72) hide show
  1. package/README.md +2 -2
  2. package/dist/browser-runtime.js +3458 -356
  3. package/dist/browser-runtime.js.map +4 -4
  4. package/dist/cmcd-producer.d.ts +5 -1
  5. package/dist/cmcd-producer.d.ts.map +1 -1
  6. package/dist/cmcd-producer.js.map +2 -2
  7. package/dist/delivery-catalog.js +18 -2
  8. package/dist/delivery-catalog.js.map +2 -2
  9. package/dist/fec-manager.d.ts +21 -16
  10. package/dist/fec-manager.d.ts.map +1 -1
  11. package/dist/fec-manager.js +177 -80
  12. package/dist/fec-manager.js.map +3 -3
  13. package/dist/held-layer-admission.d.ts +58 -0
  14. package/dist/held-layer-admission.d.ts.map +1 -0
  15. package/dist/held-layer-admission.js +105 -0
  16. package/dist/held-layer-admission.js.map +7 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js.map +2 -2
  19. package/dist/mmtp-container-parser.no-modules.js +2682 -1023
  20. package/dist/mmtp-container-parser.no-modules.js.map +4 -4
  21. package/dist/mmtp-fec-session.d.ts +36 -3
  22. package/dist/mmtp-fec-session.d.ts.map +1 -1
  23. package/dist/mmtp-fec-session.js +114 -5
  24. package/dist/mmtp-fec-session.js.map +3 -3
  25. package/dist/mmtp-router.d.ts +72 -28
  26. package/dist/mmtp-router.d.ts.map +1 -1
  27. package/dist/mmtp-router.js +321 -203
  28. package/dist/mmtp-router.js.map +2 -2
  29. package/dist/multicast-auth-catalog.d.ts +10 -2
  30. package/dist/multicast-auth-catalog.d.ts.map +1 -1
  31. package/dist/multicast-auth-catalog.js +422 -22
  32. package/dist/multicast-auth-catalog.js.map +4 -4
  33. package/dist/multicast-manager.d.ts +48 -9
  34. package/dist/multicast-manager.d.ts.map +1 -1
  35. package/dist/multicast-manager.js +3406 -340
  36. package/dist/multicast-manager.js.map +4 -4
  37. package/dist/multicast-transport.d.ts +4 -1
  38. package/dist/multicast-transport.d.ts.map +1 -1
  39. package/dist/multicast-transport.js +20 -3
  40. package/dist/multicast-transport.js.map +2 -2
  41. package/dist/no-modules.d.ts +1 -2
  42. package/dist/no-modules.d.ts.map +1 -1
  43. package/dist/no-modules.js +2668 -1007
  44. package/dist/no-modules.js.map +4 -4
  45. package/dist/repair-fetch.d.ts +10 -1
  46. package/dist/repair-fetch.d.ts.map +1 -1
  47. package/dist/repair-fetch.js +5 -2
  48. package/dist/repair-fetch.js.map +2 -2
  49. package/package.json +5 -14
  50. package/src/cmcd-producer.ts +6 -1
  51. package/src/fec-manager.ts +121 -131
  52. package/src/held-layer-admission.ts +161 -0
  53. package/src/index.ts +5 -9
  54. package/src/mmtp-fec-session.ts +76 -3
  55. package/src/mmtp-router.ts +390 -272
  56. package/src/multicast-auth-catalog.ts +81 -8
  57. package/src/multicast-manager.ts +189 -48
  58. package/src/multicast-transport.ts +6 -2
  59. package/src/no-modules.ts +1 -6
  60. package/src/repair-fetch.ts +24 -4
  61. package/dist/manifest-auth-plan.d.ts +0 -31
  62. package/dist/manifest-auth-plan.d.ts.map +0 -1
  63. package/dist/manifest-auth-plan.js +0 -426
  64. package/dist/manifest-auth-plan.js.map +0 -7
  65. package/dist/source-auth.d.ts +0 -2
  66. package/dist/source-auth.d.ts.map +0 -1
  67. package/dist/source-auth.js +0 -3912
  68. package/dist/source-auth.js.map +0 -7
  69. package/src/manifest-auth-plan.ts +0 -55
  70. package/src/source-auth.ts +0 -1
  71. package/test-vectors/generate-source-auth-fixture.mjs +0 -114
  72. package/test-vectors/source-auth-v1.json +0 -61
@@ -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
- * RTT admission: send only a FETCH the measured round trip says can land
660
- * before the entry's deadline. A refused entry is released, not queued: the
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
- if (rttMs !== null && rttMs < budgetMs) return rttMs
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
- rttMs === null ? 'rtt-unmeasured' : 'rtt-exceeds-budget',
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