Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DRM: re-implement singleLicensePer handling #1056

Merged
merged 1 commit into from
Jan 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/core/decrypt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
## Overview ####################################################################

This directory exports the `ContentDecryptor`, which allows to easily interface
with the browser APIs for decrypting a crypted content, it follows the
with the browser APIs for decrypting an encrypted content, it follows the
[Encrypted Media Extensions recommandations](https://www.w3.org/TR/encrypted-media/).

The `ContentDecryptor` is a module isolated from the rest of the player taking
Expand Down
858 changes: 582 additions & 276 deletions src/core/decrypt/content_decryptor.ts

Large diffs are not rendered by default.

110 changes: 53 additions & 57 deletions src/core/decrypt/create_or_load_session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,47 +19,13 @@ import log from "../../log";
import { CancellationSignal } from "../../utils/task_canceller";
import createSession from "./create_session";
import {
IInitializationDataInfo,
IProcessedProtectionData,
IMediaKeySessionStores,
MediaKeySessionLoadingType,
} from "./types";
import cleanOldLoadedSessions, {
ICleaningOldSessionDataPayload,
} from "./utils/clean_old_loaded_sessions";
import cleanOldLoadedSessions from "./utils/clean_old_loaded_sessions";
import isSessionUsable from "./utils/is_session_usable";

/** Information concerning a MediaKeySession. */
export interface IMediaKeySessionContext {
/** The MediaKeySession itself. */
mediaKeySession : MediaKeySession |
ICustomMediaKeySession;
/** The type of MediaKeySession (e.g. "temporary"). */
sessionType : MediaKeySessionType;
/** Initialization data assiociated to this MediaKeySession. */
initializationData : IInitializationDataInfo;
}

/** Event emitted when a new MediaKeySession has been created. */
export interface ICreatedSession {
type : "created-session";
value : IMediaKeySessionContext;
}

/** Event emitted when an already-loaded MediaKeySession is used. */
export interface ILoadedOpenSession {
type : "loaded-open-session";
value : IMediaKeySessionContext;
}

/** Event emitted when a persistent MediaKeySession has been loaded. */
export interface ILoadedPersistentSessionEvent {
type : "loaded-persistent-session";
value : IMediaKeySessionContext;
}

/** Every possible result returned by `getSession`. */
export type IGetSessionResult = ICreatedSession |
ILoadedOpenSession |
ILoadedPersistentSessionEvent;
import KeySessionRecord from "./utils/key_session_record";

/**
* Handle MediaEncryptedEvents sent by a HTMLMediaElement:
Expand All @@ -75,53 +41,49 @@ export type IGetSessionResult = ICreatedSession |
* @param {Object} stores
* @param {string} wantedSessionType
* @param {number} maxSessionCacheSize
* @param {Function} onCleaningSession
* @param {Object} cancelSignal
* @returns {Observable}
* @returns {Promise}
*/
export default async function getSession(
initializationData : IInitializationDataInfo,
export default async function createOrLoadSession(
initializationData : IProcessedProtectionData,
stores : IMediaKeySessionStores,
wantedSessionType : MediaKeySessionType,
maxSessionCacheSize : number,
onCleaningSession : (arg : ICleaningOldSessionDataPayload) => void,
cancelSignal : CancellationSignal
) : Promise<IGetSessionResult> {
/**
* Store previously-loaded MediaKeySession with the same initialization data, if one.
*/
) : Promise<ICreateOrLoadSessionResult> {
/** Store previously-loaded compatible MediaKeySession, if one. */
let previousLoadedSession : MediaKeySession |
ICustomMediaKeySession |
null = null;

const { loadedSessionsStore, persistentSessionsStore } = stores;
const entry = loadedSessionsStore.getAndReuse(initializationData);
const entry = loadedSessionsStore.reuse(initializationData);
if (entry !== null) {
previousLoadedSession = entry.mediaKeySession;
if (isSessionUsable(previousLoadedSession)) {
log.info("DRM: Reuse loaded session", previousLoadedSession.sessionId);
return { type: "loaded-open-session" as const,
return { type: MediaKeySessionLoadingType.LoadedOpenSession,
value: { mediaKeySession: previousLoadedSession,
sessionType: entry.sessionType,
initializationData } };
keySessionRecord: entry.keySessionRecord } };
} else if (persistentSessionsStore !== null) {
// If the session is not usable anymore, we can also remove it from the
// PersistentSessionsStore.
// TODO Are we sure this is always what we want?
persistentSessionsStore.delete(initializationData);
if (entry.mediaKeySession.sessionId !== "") {
persistentSessionsStore.delete(entry.mediaKeySession.sessionId);
}
}
}

if (previousLoadedSession) {
await loadedSessionsStore.closeSession(initializationData);
if (previousLoadedSession !== null) {
await loadedSessionsStore.closeSession(previousLoadedSession);
if (cancelSignal.cancellationError !== null) {
throw cancelSignal.cancellationError; // stop here if cancelled since
}
}

await cleanOldLoadedSessions(loadedSessionsStore,
maxSessionCacheSize,
onCleaningSession);
await cleanOldLoadedSessions(loadedSessionsStore, maxSessionCacheSize);
if (cancelSignal.cancellationError !== null) {
throw cancelSignal.cancellationError; // stop here if cancelled since
}
Expand All @@ -130,5 +92,39 @@ export default async function getSession(
return { type: evt.type,
value: { mediaKeySession: evt.value.mediaKeySession,
sessionType: evt.value.sessionType,
initializationData } };
keySessionRecord: evt.value.keySessionRecord } };
}

/** Information concerning a MediaKeySession. */
export interface IMediaKeySessionContext {
/** The MediaKeySession itself. */
mediaKeySession : MediaKeySession |
ICustomMediaKeySession;
/** The type of MediaKeySession (e.g. "temporary"). */
sessionType : MediaKeySessionType;
/** `KeySessionRecord` assiociated to this MediaKeySession. */
keySessionRecord : KeySessionRecord;
}

/** Event emitted when a new MediaKeySession has been created. */
export interface ICreatedSession {
type : MediaKeySessionLoadingType.Created;
value : IMediaKeySessionContext;
}

/** Event emitted when an already-loaded MediaKeySession is used. */
export interface ILoadedOpenSession {
type : MediaKeySessionLoadingType.LoadedOpenSession;
value : IMediaKeySessionContext;
}

/** Event emitted when a persistent MediaKeySession has been loaded. */
export interface ILoadedPersistentSessionEvent {
type : MediaKeySessionLoadingType.LoadedPersistentSession;
value : IMediaKeySessionContext;
}

/** Every possible result returned by `createOrLoadSession`. */
export type ICreateOrLoadSessionResult = ICreatedSession |
ILoadedOpenSession |
ILoadedPersistentSessionEvent;
120 changes: 63 additions & 57 deletions src/core/decrypt/create_session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,62 +21,48 @@ import {
} from "../../compat";
import log from "../../log";
import {
IInitializationDataInfo,
IProcessedProtectionData,
IMediaKeySessionStores,
MediaKeySessionLoadingType,
} from "./types";
import isSessionUsable from "./utils/is_session_usable";
import KeySessionRecord from "./utils/key_session_record";
import LoadedSessionsStore from "./utils/loaded_sessions_store";
import PersistentSessionsStore from "./utils/persistent_sessions_store";

export interface INewSessionCreatedEvent {
type : "created-session";
value : { mediaKeySession : MediaKeySession |
ICustomMediaKeySession;
sessionType : MediaKeySessionType; };
}

export interface IPersistentSessionRecoveryEvent {
type : "loaded-persistent-session";
value : { mediaKeySession : MediaKeySession |
ICustomMediaKeySession;
sessionType : MediaKeySessionType; };
}

export type ICreateSessionEvent = INewSessionCreatedEvent |
IPersistentSessionRecoveryEvent;

/**
* Create a new Session on the given MediaKeys, corresponding to the given
* initializationData.
* Create a new Session or load a persistent one on the given MediaKeys,
* according to wanted settings and what is currently stored.
*
* If session creating fails, remove the oldest MediaKeySession loaded and
* retry.
*
* /!\ This only creates new sessions.
* It will fail if loadedSessionsStore already has a MediaKeySession with
* the given initializationData.
* @param {Uint8Array} initData
* @param {string|undefined} initDataType
* @param {Object} mediaKeysInfos
* the given initialization data.
* @param {Object} stores
* @param {Object} initData
* @param {string} wantedSessionType
* @returns {Promise}
*/
export default function createSession(
stores : IMediaKeySessionStores,
initializationData : IInitializationDataInfo,
initData : IProcessedProtectionData,
wantedSessionType : MediaKeySessionType
) : Promise<ICreateSessionEvent> {
const { loadedSessionsStore,
persistentSessionsStore } = stores;

if (wantedSessionType === "temporary") {
return createTemporarySession(loadedSessionsStore, initializationData);
return createTemporarySession(loadedSessionsStore, initData);
} else if (persistentSessionsStore === null) {
log.warn("DRM: Cannot create persistent MediaKeySession, " +
"PersistentSessionsStore not created.");
return createTemporarySession(loadedSessionsStore, initializationData);
return createTemporarySession(loadedSessionsStore, initData);
}
return createAndTryToRetrievePersistentSession(loadedSessionsStore,
persistentSessionsStore,
initializationData);
initData);
}

/**
Expand All @@ -88,18 +74,17 @@ export default function createSession(
*/
function createTemporarySession(
loadedSessionsStore : LoadedSessionsStore,
initData : IInitializationDataInfo
initData : IProcessedProtectionData
) : Promise<INewSessionCreatedEvent> {
log.info("DRM: Creating a new temporary session");
const session = loadedSessionsStore.createSession(initData, "temporary");
return PPromise.resolve({ type: "created-session" as const,
value: { mediaKeySession: session,
sessionType: "temporary" as const } });
const entry = loadedSessionsStore.createSession(initData, "temporary");
return PPromise.resolve({ type: MediaKeySessionLoadingType.Created,
value: entry });
}

/**
* Create a persistent MediaKeySession and try to load on it a previous
* MediaKeySession linked to the same initData and initDataType.
* MediaKeySession linked to the same initialization data.
* @param {Object} loadedSessionsStore
* @param {Object} persistentSessionsStore
* @param {Object} initData
Expand All @@ -108,34 +93,32 @@ function createTemporarySession(
async function createAndTryToRetrievePersistentSession(
loadedSessionsStore : LoadedSessionsStore,
persistentSessionsStore : PersistentSessionsStore,
initData : IInitializationDataInfo
initData : IProcessedProtectionData
) : Promise<INewSessionCreatedEvent | IPersistentSessionRecoveryEvent> {
log.info("DRM: Creating persistent MediaKeySession");

const session = loadedSessionsStore.createSession(initData, "persistent-license");
const entry = loadedSessionsStore.createSession(initData, "persistent-license");
const storedEntry = persistentSessionsStore.getAndReuse(initData);
if (storedEntry === null) {
return { type: "created-session" as const,
value: { mediaKeySession: session,
sessionType: "persistent-license" as const } };
return { type: MediaKeySessionLoadingType.Created,
value: entry };
}

try {
const hasLoadedSession = await loadSession(session, storedEntry.sessionId);
const hasLoadedSession = await loadSession(entry.mediaKeySession,
storedEntry.sessionId);
if (!hasLoadedSession) {
log.warn("DRM: No data stored for the loaded session");
persistentSessionsStore.delete(initData);
return { type: "created-session" as const,
value: { mediaKeySession: session,
sessionType: "persistent-license" } };
persistentSessionsStore.delete(storedEntry.sessionId);
return { type: MediaKeySessionLoadingType.Created,
value: entry };
}

if (hasLoadedSession && isSessionUsable(session)) {
persistentSessionsStore.add(initData, session);
if (hasLoadedSession && isSessionUsable(entry.mediaKeySession)) {
persistentSessionsStore.add(initData, initData.keyIds, entry.mediaKeySession);
log.info("DRM: Succeeded to load persistent session.");
return { type: "loaded-persistent-session" as const,
value: { mediaKeySession: session,
sessionType: "persistent-license" } };
return { type: MediaKeySessionLoadingType.LoadedPersistentSession,
value: entry };
}

// Unusable persistent session: recreate a new session from scratch.
Expand All @@ -155,15 +138,38 @@ async function createAndTryToRetrievePersistentSession(
*/
async function recreatePersistentSession() : Promise<INewSessionCreatedEvent> {
log.info("DRM: Removing previous persistent session.");
if (persistentSessionsStore.get(initData) !== null) {
persistentSessionsStore.delete(initData);
const persistentEntry = persistentSessionsStore.get(initData);
if (persistentEntry !== null) {
persistentSessionsStore.delete(persistentEntry.sessionId);
}

await loadedSessionsStore.closeSession(initData);
const newSession = loadedSessionsStore.createSession(initData,
"persistent-license");
return { type: "created-session" as const,
value: { mediaKeySession: newSession,
sessionType: "persistent-license" } };
await loadedSessionsStore.closeSession(entry.mediaKeySession);
const newEntry = loadedSessionsStore.createSession(initData,
"persistent-license");
return { type: MediaKeySessionLoadingType.Created,
value: newEntry };
}
}

export interface INewSessionCreatedEvent {
type : MediaKeySessionLoadingType.Created;
value : {
mediaKeySession : MediaKeySession |
ICustomMediaKeySession;
sessionType : MediaKeySessionType;
keySessionRecord : KeySessionRecord;
};
}

export interface IPersistentSessionRecoveryEvent {
type : MediaKeySessionLoadingType.LoadedPersistentSession;
value : {
mediaKeySession : MediaKeySession |
ICustomMediaKeySession;
sessionType : MediaKeySessionType;
keySessionRecord : KeySessionRecord;
};
}

export type ICreateSessionEvent = INewSessionCreatedEvent |
IPersistentSessionRecoveryEvent;
Loading