@blockcast/mmt-container 0.4.1 → 0.5.0-main.34c7b9c2cfa0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +2 -2
  2. package/dist/browser-runtime.js +3769 -364
  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 +197 -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 +1 -1
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js.map +2 -2
  20. package/dist/mmtp-container-parser.d.ts +10 -0
  21. package/dist/mmtp-container-parser.d.ts.map +1 -1
  22. package/dist/mmtp-container-parser.js +31 -1
  23. package/dist/mmtp-container-parser.js.map +2 -2
  24. package/dist/mmtp-container-parser.no-modules.js +2713 -1024
  25. package/dist/mmtp-container-parser.no-modules.js.map +4 -4
  26. package/dist/mmtp-decode-pipeline.js +31 -1
  27. package/dist/mmtp-decode-pipeline.js.map +2 -2
  28. package/dist/mmtp-fec-session.d.ts +36 -3
  29. package/dist/mmtp-fec-session.d.ts.map +1 -1
  30. package/dist/mmtp-fec-session.js +114 -5
  31. package/dist/mmtp-fec-session.js.map +3 -3
  32. package/dist/mmtp-router.d.ts +96 -29
  33. package/dist/mmtp-router.d.ts.map +1 -1
  34. package/dist/mmtp-router.js +522 -205
  35. package/dist/mmtp-router.js.map +2 -2
  36. package/dist/multicast-auth-catalog.d.ts +10 -2
  37. package/dist/multicast-auth-catalog.d.ts.map +1 -1
  38. package/dist/multicast-auth-catalog.js +422 -22
  39. package/dist/multicast-auth-catalog.js.map +4 -4
  40. package/dist/multicast-manager.d.ts +78 -11
  41. package/dist/multicast-manager.d.ts.map +1 -1
  42. package/dist/multicast-manager.js +3663 -333
  43. package/dist/multicast-manager.js.map +4 -4
  44. package/dist/multicast-transport.d.ts +4 -1
  45. package/dist/multicast-transport.d.ts.map +1 -1
  46. package/dist/multicast-transport.js +20 -3
  47. package/dist/multicast-transport.js.map +2 -2
  48. package/dist/no-modules.d.ts +1 -2
  49. package/dist/no-modules.d.ts.map +1 -1
  50. package/dist/no-modules.js +2699 -1008
  51. package/dist/no-modules.js.map +4 -4
  52. package/dist/repair-fetch.d.ts +50 -10
  53. package/dist/repair-fetch.d.ts.map +1 -1
  54. package/dist/repair-fetch.js +62 -20
  55. package/dist/repair-fetch.js.map +2 -2
  56. package/dist/wasm-mmtp-container-parser.js +31 -1
  57. package/dist/wasm-mmtp-container-parser.js.map +2 -2
  58. package/package.json +7 -15
  59. package/src/cmcd-producer.ts +6 -1
  60. package/src/fec-manager.ts +147 -131
  61. package/src/held-layer-admission.ts +161 -0
  62. package/src/index.ts +6 -9
  63. package/src/mmtp-container-parser.ts +48 -0
  64. package/src/mmtp-fec-session.ts +76 -3
  65. package/src/mmtp-router.ts +637 -274
  66. package/src/multicast-auth-catalog.ts +81 -8
  67. package/src/multicast-manager.ts +274 -49
  68. package/src/multicast-transport.ts +6 -2
  69. package/src/no-modules.ts +1 -6
  70. package/src/repair-fetch.ts +154 -34
  71. package/test-vectors/catalog/negative/multicast-auth-padded-key.json +0 -1
  72. package/test-vectors/catalog/negative/multicast-auth-reused-track-format.json +0 -1
  73. package/test-vectors/catalog/negative/multicast-auth-root-dotted-key.json +0 -1
  74. package/test-vectors/catalog/negative/multicast-auth-trailing-pad-bits.json +0 -1
  75. package/test-vectors/repair-policy-v1.json +113 -0
  76. package/dist/manifest-auth-plan.d.ts +0 -31
  77. package/dist/manifest-auth-plan.d.ts.map +0 -1
  78. package/dist/manifest-auth-plan.js +0 -426
  79. package/dist/manifest-auth-plan.js.map +0 -7
  80. package/dist/source-auth.d.ts +0 -2
  81. package/dist/source-auth.d.ts.map +0 -1
  82. package/dist/source-auth.js +0 -3912
  83. package/dist/source-auth.js.map +0 -7
  84. package/src/manifest-auth-plan.ts +0 -55
  85. package/src/source-auth.ts +0 -1
  86. package/test-vectors/generate-source-auth-fixture.mjs +0 -114
  87. package/test-vectors/source-auth-v1.json +0 -61
@@ -16,7 +16,7 @@
16
16
  */
17
17
 
18
18
  import { multicastCatalogIssues } from "@blockcast/mmt-base";
19
- import { manifestKeyFor, type ManifestKey } from "@blockcast/mmt-manifest-verify";
19
+ import { bcProvenanceV2Ctx, type BcProvenanceV2Params, type BcpV2Key } from "@blockcast/mmt-manifest-verify";
20
20
 
21
21
  export type MulticastAuthScheme = "bc-provenance";
22
22
  export type MulticastAuthSig = "ed25519";
@@ -153,11 +153,13 @@ export interface ResolvedMulticastAuthFlow {
153
153
  manifestTimerMs: number;
154
154
  manifestTrack?: string;
155
155
  tracks: Array<{ name: string; packetId: number }>;
156
+ /** `ctx` recomputed from the catalog (7.2.4); present when `namespaceFields` was given. */
157
+ ctx?: Uint8Array;
156
158
  }
157
159
 
158
160
  export interface ResolvedMulticastAuthConfig {
159
161
  /** Decoded keys, ascending by `firstEpoch`. */
160
- keys: ManifestKey[];
162
+ keys: BcpV2Key[];
161
163
  minEpoch: number;
162
164
  flows: ResolvedMulticastAuthFlow[];
163
165
  }
@@ -171,10 +173,19 @@ export interface ResolvedMulticastAuthConfig {
171
173
  * `firstEpoch`, the retired root key) throws, so nothing is admitted and
172
174
  * nothing falls back to unauthenticated reception. An endpoint the rules make
173
175
  * a receiver ignore yields no flow; the others still bind.
176
+ *
177
+ * `namespaceFields` are the Track Namespace Fields of the catalog track, as
178
+ * MOQT carries them. With them every flow carries the recomputed `ctx`, which
179
+ * the receiver compares against each record; without them `ctx` is carried
180
+ * blind (7.2.4).
174
181
  */
175
182
  export function resolveMulticastAuthCatalog(
176
183
  catalog: unknown,
177
- options: { requireAuth?: boolean; catalogNamespace?: string } = {},
184
+ options: {
185
+ requireAuth?: boolean;
186
+ catalogNamespace?: string;
187
+ namespaceFields?: readonly (string | Uint8Array)[];
188
+ } = {},
178
189
  ): ResolvedMulticastAuthConfig | undefined {
179
190
  if (typeof catalog !== "object" || catalog === null || Array.isArray(catalog)) {
180
191
  throw new Error("[multicast-auth-catalog] catalog must be an object");
@@ -192,10 +203,22 @@ export function resolveMulticastAuthCatalog(
192
203
  }
193
204
  const ignored = new Set(issues.map((issue) => issue.endpointIndex));
194
205
  const endpoints = (multicast!["endpoints"] ?? []) as Array<Record<string, unknown>>;
206
+ const catalogTracks = ((catalog as Record<string, unknown>)["tracks"] ?? []) as Array<Record<string, unknown>>;
195
207
  const flows: ResolvedMulticastAuthFlow[] = [];
196
208
  endpoints.forEach((endpoint, endpointIndex) => {
197
209
  if (ignored.has(endpointIndex)) return;
198
210
  const flowAuth = endpoint["auth"] as EndpointAuthConfig;
211
+ const tracks = ((endpoint["tracks"] ?? []) as Array<{ name: string; packetId: number }>).map(({ name, packetId }) => ({
212
+ name,
213
+ packetId,
214
+ }));
215
+ let ctx: Uint8Array | undefined;
216
+ if (options.namespaceFields !== undefined) {
217
+ const params = ctxParams(catalogTracks, tracks);
218
+ // No catalog entry to hash: the endpoint is ignored, nothing is read unauthenticated.
219
+ if (params === undefined) return;
220
+ ctx = bcProvenanceV2Ctx(options.namespaceFields, tracks.map((track) => track.packetId), params);
221
+ }
199
222
  flows.push({
200
223
  endpointIndex,
201
224
  source: (endpoint["sourceAddress"] ?? flowAuth.senderAddress) as string,
@@ -207,10 +230,8 @@ export function resolveMulticastAuthCatalog(
207
230
  maxAuthDelayMs: flowAuth.maxAuthDelayMs ?? DEFAULT_MAX_AUTH_DELAY_MS,
208
231
  manifestTimerMs: flowAuth.manifestTimerMs ?? DEFAULT_MANIFEST_TIMER_MS,
209
232
  ...(flowAuth.manifestTrack === undefined ? {} : { manifestTrack: flowAuth.manifestTrack }),
210
- tracks: ((endpoint["tracks"] ?? []) as Array<{ name: string; packetId: number }>).map(({ name, packetId }) => ({
211
- name,
212
- packetId,
213
- })),
233
+ tracks,
234
+ ...(ctx === undefined ? {} : { ctx }),
214
235
  });
215
236
  });
216
237
  return {
@@ -222,6 +243,55 @@ export function resolveMulticastAuthCatalog(
222
243
  };
223
244
  }
224
245
 
246
+ /** A positive integer that fits the u32 it is hashed as (7.2.4), else `0`. */
247
+ function positiveInteger(value: unknown): number {
248
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0 && value <= 0xffffffff ? value : 0;
249
+ }
250
+
251
+ /**
252
+ * `params_hash` inputs (7.2.4): the catalog entry of the endpoint's
253
+ * lowest-`packetId` media track or, for a repair-only endpoint, of the source
254
+ * its lowest-`packetId` repair track repairs (`depends`, else the track whose
255
+ * `fec.repairTrack` names it). A timescale or group duration that does not
256
+ * resolve to an integer tick count is `0`.
257
+ */
258
+ function ctxParams(
259
+ catalogTracks: Array<Record<string, unknown>>,
260
+ listed: Array<{ name: string; packetId: number }>,
261
+ ): BcProvenanceV2Params | undefined {
262
+ const byName = new Map(catalogTracks.map((track) => [track["name"], track]));
263
+ const ordered = [...listed].sort((a, b) => a.packetId - b.packetId).map(({ name }) => byName.get(name));
264
+ let entry = ordered.find((track) => track !== undefined && track["packaging"] !== "fec-repair");
265
+ if (entry === undefined) {
266
+ const repair = ordered.find((track) => track !== undefined);
267
+ if (repair === undefined) return undefined;
268
+ const depends = repair["depends"];
269
+ entry = Array.isArray(depends) && depends.length === 1
270
+ ? byName.get(depends[0])
271
+ : catalogTracks.find((track) => (track["fec"] as Record<string, unknown> | undefined)?.["repairTrack"] === repair["name"]);
272
+ if (entry === undefined) return undefined;
273
+ }
274
+ const timescale = positiveInteger(entry["timescale"]);
275
+ const ms = positiveInteger(entry["groupDurationMs"]);
276
+ const groupDurationTicks = positiveInteger(entry["groupDurationTicks"]) ||
277
+ positiveInteger(timescale && (ms * timescale) % 1000 === 0 ? (ms * timescale) / 1000 : 0);
278
+ const fec = entry["fec"] as Record<string, unknown> | undefined;
279
+ return {
280
+ codec: typeof entry["codec"] === "string" ? entry["codec"] : "",
281
+ timescale,
282
+ groupDurationTicks,
283
+ ...(fec === undefined || fec === null ? {} : {
284
+ fec: {
285
+ algorithm: typeof fec["algorithm"] === "string" ? fec["algorithm"] : "",
286
+ sourceSymbols: positiveInteger(fec["sourceSymbols"]),
287
+ repairSymbols: positiveInteger(fec["repairSymbols"]),
288
+ symbolSize: positiveInteger(fec["symbolSize"]),
289
+ interleaveDepthMs: positiveInteger(fec["interleaveDepthMs"]),
290
+ },
291
+ }),
292
+ };
293
+ }
294
+
225
295
  /** The key that signs a record of `kid` at `epoch`, or `undefined` when that
226
296
  * `kid` does not sign it (7.2.7: such a record is dropped before verification). */
227
297
  export function multicastAuthKeyFor(
@@ -229,5 +299,8 @@ export function multicastAuthKeyFor(
229
299
  kid: number,
230
300
  epoch: number,
231
301
  ): Uint8Array | undefined {
232
- return manifestKeyFor(resolved.keys, epoch, kid);
302
+ // `keys` is ascending by firstEpoch with unique kid and firstEpoch (resolve throws otherwise).
303
+ const signers = resolved.keys.filter((key) => key.firstEpoch <= epoch);
304
+ const signer = signers[signers.length - 1];
305
+ return signer?.kid === kid ? signer.publicKey : undefined;
233
306
  }