import type { ErrorCode } from "./error"; import { gateSequencerCodec, parseSequencerCodecProbeRequest, parseSequencerCodecProbeResult, type SequencerCodecProbeRequestIR, type SequencerCodecProbeResultIR, } from "./sequencer"; export const SEQUENCER_MEDIA_CACHE_SCHEMA = 1 as const; export const SEQUENCER_MEDIA_PROXY_MAX_BYTES = 64 * 1024 * 1024; export interface SequencerMediaProxyProfileIR { kind: "MOVIE_RGBA8_FRAME"; width: number; height: number; colorSpace: "SRGB8"; alphaMode: "STRAIGHT"; } export interface SequencerMediaCacheManifestIR { schemaVersion: typeof SEQUENCER_MEDIA_CACHE_SCHEMA; source: SequencerCodecProbeRequestIR; decodeCapability: SequencerCodecProbeResultIR; profile: SequencerMediaProxyProfileIR; sourceFrame: number; identitySha256: string; payloadByteLength: number; payloadSha256: string; } export class SequencerMediaCacheValidationError extends Error { readonly code: ErrorCode; constructor(code: ErrorCode, message: string) { super(`${code}: ${message}`); this.name = "SequencerMediaCacheValidationError"; this.code = code; } } const SHA256 = /^[a-f0-9]{64}$/; const MANIFEST_KEYS = new Set([ "schemaVersion", "source", "decodeCapability", "profile", "sourceFrame", "identitySha256", "payloadByteLength", "payloadSha256", ]); const PROFILE_KEYS = new Set(["kind", "width", "height", "colorSpace", "alphaMode"]); function record(value: unknown, label: string): Record { if (typeof value !== "object" || value === null || Array.isArray(value)) { throw new SequencerMediaCacheValidationError("SEQUENCER_SCHEMA_INVALID", `${label} must be an object`); } return value as Record; } function exactKeys(value: Record, allowed: ReadonlySet, label: string): void { const unexpected = Object.keys(value).filter((key) => !allowed.has(key)); if (unexpected.length > 0) { throw new SequencerMediaCacheValidationError("SEQUENCER_SCHEMA_INVALID", `${label} contains undeclared fields: ${unexpected.join(", ")}`); } } function integer(value: unknown, label: string, minimum: number, maximum: number): number { if (typeof value !== "number" || !Number.isSafeInteger(value) || value < minimum || value > maximum) { throw new SequencerMediaCacheValidationError("SEQUENCER_SCHEMA_INVALID", `${label} is outside the bounded range`); } return value; } function digest(value: unknown, label: string): string { if (typeof value !== "string" || !SHA256.test(value)) { throw new SequencerMediaCacheValidationError("SEQUENCER_SCHEMA_INVALID", `${label} must be a lowercase SHA-256 digest`); } return value; } async function sha256(data: ArrayBuffer): Promise { const result = await crypto.subtle.digest("SHA-256", data); return Array.from(new Uint8Array(result), (value) => value.toString(16).padStart(2, "0")).join(""); } function canonicalSource(source: SequencerCodecProbeRequestIR): Record { return { schemaVersion: source.schemaVersion, stripType: source.stripType, mimeType: source.mimeType, byteLength: source.byteLength, sourceSha256: source.sourceSha256, }; } function canonicalCapability(capability: SequencerCodecProbeResultIR): Record { const decoded = capability.decoded === null ? null : capability.stripType === "IMAGE" ? { width: capability.decoded.width, height: capability.decoded.height, } : capability.stripType === "SOUND" ? { sampleRate: capability.decoded.sampleRate, channels: capability.decoded.channels, durationFrames: capability.decoded.durationFrames, } : { width: capability.decoded.width, height: capability.decoded.height, durationMicros: capability.decoded.durationMicros, }; return { ...canonicalSource(capability), status: capability.status, backend: capability.backend, reason: capability.reason, decoded, }; } function canonicalProfile(profile: SequencerMediaProxyProfileIR): Record { return { kind: profile.kind, width: profile.width, height: profile.height, colorSpace: profile.colorSpace, alphaMode: profile.alphaMode, }; } function sameJson(left: unknown, right: unknown): boolean { return JSON.stringify(left) === JSON.stringify(right); } function parseReadyMovieCapability( sourceValue: unknown, capabilityValue: unknown, ): { source: SequencerCodecProbeRequestIR; capability: SequencerCodecProbeResultIR } { const source = parseSequencerCodecProbeRequest(sourceValue); const capability = parseSequencerCodecProbeResult(capabilityValue); if (source.stripType !== "MOVIE" || gateSequencerCodec(source, capability).status !== "READY") { throw new SequencerMediaCacheValidationError( "SEQUENCER_CODEC_UNSUPPORTED", "Movie proxy cache requires a source-bound READY runtime decode receipt", ); } return { source, capability }; } export function parseSequencerMediaProxyProfile(value: unknown): SequencerMediaProxyProfileIR { const profile = record(value, "Sequencer media proxy profile"); exactKeys(profile, PROFILE_KEYS, "Sequencer media proxy profile"); if (profile.kind !== "MOVIE_RGBA8_FRAME" || profile.colorSpace !== "SRGB8" || profile.alphaMode !== "STRAIGHT") { throw new SequencerMediaCacheValidationError("SEQUENCER_SCHEMA_INVALID", "Sequencer media proxy profile is unsupported"); } const width = integer(profile.width, "profile.width", 1, 16_384); const height = integer(profile.height, "profile.height", 1, 16_384); if (width * height * 4 > SEQUENCER_MEDIA_PROXY_MAX_BYTES) { throw new SequencerMediaCacheValidationError("SEQUENCER_BUDGET_EXCEEDED", "Sequencer media proxy frame exceeds the RGBA8 budget"); } return { kind: "MOVIE_RGBA8_FRAME", width, height, colorSpace: "SRGB8", alphaMode: "STRAIGHT" }; } export async function computeSequencerMediaCacheIdentity( sourceValue: unknown, capabilityValue: unknown, profileValue: unknown, sourceFrameValue: unknown, ): Promise { const { source, capability } = parseReadyMovieCapability(sourceValue, capabilityValue); const profile = parseSequencerMediaProxyProfile(profileValue); const sourceFrame = integer(sourceFrameValue, "sourceFrame", 0, 1_000_000); const identity = JSON.stringify({ schemaVersion: SEQUENCER_MEDIA_CACHE_SCHEMA, source: canonicalSource(source), decodeCapability: canonicalCapability(capability), profile: canonicalProfile(profile), sourceFrame, }); return sha256(new TextEncoder().encode(identity).buffer as ArrayBuffer); } export function parseSequencerMediaCacheManifest(value: unknown): SequencerMediaCacheManifestIR { const manifest = record(value, "Sequencer media cache manifest"); exactKeys(manifest, MANIFEST_KEYS, "Sequencer media cache manifest"); if (manifest.schemaVersion !== SEQUENCER_MEDIA_CACHE_SCHEMA) { throw new SequencerMediaCacheValidationError("PROTOCOL_MISMATCH", "Unsupported Sequencer media cache schema"); } const { source, capability } = parseReadyMovieCapability(manifest.source, manifest.decodeCapability); const profile = parseSequencerMediaProxyProfile(manifest.profile); if (capability.decoded === null || capability.decoded.width === undefined || capability.decoded.height === undefined || profile.width > capability.decoded.width || profile.height > capability.decoded.height) { throw new SequencerMediaCacheValidationError("SEQUENCER_SCHEMA_INVALID", "Proxy profile exceeds the runtime decoded movie dimensions"); } const sourceFrame = integer(manifest.sourceFrame, "sourceFrame", 0, 1_000_000); const payloadByteLength = integer(manifest.payloadByteLength, "payloadByteLength", 1, SEQUENCER_MEDIA_PROXY_MAX_BYTES); if (payloadByteLength !== profile.width * profile.height * 4) { throw new SequencerMediaCacheValidationError("SEQUENCER_SCHEMA_INVALID", "Proxy payload length does not match its RGBA8 profile"); } return { schemaVersion: SEQUENCER_MEDIA_CACHE_SCHEMA, source, decodeCapability: capability, profile, sourceFrame, identitySha256: digest(manifest.identitySha256, "identitySha256"), payloadByteLength, payloadSha256: digest(manifest.payloadSha256, "payloadSha256"), }; } export async function createSequencerMediaCacheManifest( sourceValue: unknown, capabilityValue: unknown, profileValue: unknown, sourceFrameValue: unknown, payload: ArrayBuffer, ): Promise { if (!(payload instanceof ArrayBuffer) || payload.byteLength === 0 || payload.byteLength > SEQUENCER_MEDIA_PROXY_MAX_BYTES) { throw new SequencerMediaCacheValidationError("SEQUENCER_BUDGET_EXCEEDED", "Sequencer media proxy payload exceeds the byte budget"); } const { source, capability } = parseReadyMovieCapability(sourceValue, capabilityValue); const profile = parseSequencerMediaProxyProfile(profileValue); if (capability.decoded === null || capability.decoded.width === undefined || capability.decoded.height === undefined || profile.width > capability.decoded.width || profile.height > capability.decoded.height) { throw new SequencerMediaCacheValidationError("SEQUENCER_SCHEMA_INVALID", "Proxy profile exceeds the runtime decoded movie dimensions"); } const sourceFrame = integer(sourceFrameValue, "sourceFrame", 0, 1_000_000); const manifest = { schemaVersion: SEQUENCER_MEDIA_CACHE_SCHEMA, source, decodeCapability: capability, profile, sourceFrame, identitySha256: await computeSequencerMediaCacheIdentity(source, capability, profile, sourceFrame), payloadByteLength: payload.byteLength, payloadSha256: await sha256(payload), } satisfies SequencerMediaCacheManifestIR; return parseSequencerMediaCacheManifest(manifest); } export async function verifySequencerMediaCacheEntry( manifestValue: unknown, payload: ArrayBuffer, currentSourceValue: unknown, currentCapabilityValue: unknown, ): Promise { const manifest = parseSequencerMediaCacheManifest(manifestValue); const { source: currentSource, capability: currentCapability } = parseReadyMovieCapability( currentSourceValue, currentCapabilityValue, ); if (!sameJson(canonicalSource(manifest.source), canonicalSource(currentSource))) { throw new SequencerMediaCacheValidationError("SEQUENCER_CACHE_SOURCE_MISMATCH", "Proxy cache source identity is stale"); } if (!sameJson(canonicalCapability(manifest.decodeCapability), canonicalCapability(currentCapability))) { throw new SequencerMediaCacheValidationError("SEQUENCER_CACHE_CAPABILITY_MISMATCH", "Proxy cache decode capability is stale"); } const identitySha256 = await computeSequencerMediaCacheIdentity( manifest.source, manifest.decodeCapability, manifest.profile, manifest.sourceFrame, ); if (identitySha256 !== manifest.identitySha256) { throw new SequencerMediaCacheValidationError("SEQUENCER_CACHE_IDENTITY_MISMATCH", "Proxy cache identity hash is invalid"); } if (!(payload instanceof ArrayBuffer) || payload.byteLength !== manifest.payloadByteLength || await sha256(payload) !== manifest.payloadSha256) { throw new SequencerMediaCacheValidationError("SEQUENCER_CACHE_HASH_MISMATCH", "Proxy cache payload failed SHA-256 verification"); } return manifest; } export function sequencerMediaCacheKey(manifestValue: unknown): string { const manifest = parseSequencerMediaCacheManifest(manifestValue); return `sequencer-media-cache:v${manifest.schemaVersion}:${manifest.identitySha256}`; }