<commit_before>/**
 * Copyright 2015 CANAL+ Group
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const _ = require("canal-js-utils/misc");
const log = require("canal-js-utils/log");
const Promise_ = require("canal-js-utils/promise");
const assert = require("canal-js-utils/assert");
const { Observable } = require("canal-js-utils/rx");
const { combineLatest, empty, fromPromise, merge, just } = Observable;
const {
  KeySystemAccess,
  requestMediaKeySystemAccess,
  setMediaKeys,
  emeEvents,
} = require("./compat");

const {
  onEncrypted,
  onKeyMessage,
  onKeyError,
  onKeyStatusesChange,
} = emeEvents;

const SYSTEMS = {
  "clearkey":  ["webkit-org.w3.clearkey", "org.w3.clearkey"],
  "widevine":  ["com.widevine.alpha"],
  "playready": ["com.youtube.playready", "com.microsoft.playready"]
};

// Key statuses to error mapping. Taken from shaka-player.
const KEY_STATUS_ERRORS = {
  "output-not-allowed": "eme: the required output protection is not available.",
  "expired": "eme: a required key has expired and the content cannot be decrypted.",
  "internal-error": "eme: an unknown error has occurred in the CDM."
};

function hashBuffer(buffer) {
  let hash = 0;
  let char;
  for (let i = 0; i < buffer.length; i++) {
    char = buffer[i];
    hash = ((hash <<  5) - hash) + char;
    hash = hash & hash; // Convert to 32bit integer
  }
  return hash;
}

function hashInitData(initData) {
  if (typeof initData == "number") {
    return initData;
  } else {
    return hashBuffer(initData);
  }
}

const NotSupportedKeySystemError = () =>
  new Error("eme: could not find a compatible key system");

/**
 * Set maintaining a representation of all currently loaded
 * MediaKeySessions. This set allow to reuse sessions without re-
 * negotiating a license exchange if the key is already used in a
 * loaded session.
 */
class InMemorySessionsSet {
  constructor() {
    this._entries = [];
  }

  getFirst() {
    if (this._entries.length > 0) {
      return this._entries[0].session;
    }
  }

  get(initData) {
    initData = hashInitData(initData);
    const entry = _.find(
      this._entries,
      entry => entry.initData === initData
    );
    if (entry) {
      return entry.session;
    }
  }

  add(initData, session) {
    initData = hashInitData(initData);
    if (!this.get(initData)) {
      const sessionId = session.sessionId;
      if (!sessionId) {
        return;
      }

      const entry = { session, initData };
      log.debug("eme-mem-store: add session", entry);
      this._entries.push(entry);
      session.closed.then(() => {
        log.debug("eme-mem-store: remove closed session", entry);
        const idx = this._entries.indexOf(entry);
        if (idx >= 0) {
          this._entries.splice(idx, 1);
        }
      });
    }
  }

  delete(sessionId) {
    const entry = _.find(
      this._entries,
      entry => entry.session.sessionId === sessionId
    );
    if (entry) {
      log.debug("eme-mem-store: delete session", sessionId);
      const idx = this._entries.indexOf(entry);
      this._entries.splice(idx, 1);
      return entry.session;
    }
  }

  deleteAndClose(sessionId) {
    const session = this.delete(sessionId);
    if (session) {
      log.debug("eme-mem-store: close session", sessionId);
      return session.close();
    } else {
      return Promise_.resolve();
    }
  }

  dispose() {
    const disposed = this._entries.map(({ session }) => session.close());
    this._entires = [];
    return Promise_.all(disposed);
  }
}

/**
 * Set representing persisted licenses. Depends on a simple local-
 * storage implementation with a `save`/`load` synchronous interface
 * to persist informations on persisted sessions.
 *
 * This set is used only for a cdm/keysystem with license persistency
 * supported.
 */
class PersistedSessionsSet {
  constructor(storage) {
    this.setStorage(storage);
  }

  setStorage(storage) {
    if (this._storage === storage) {
      return;
    }

    assert(
      storage,
      `eme: no licenseStorage given for keySystem with persistentLicense`
    );

    assert.iface(
      storage,
      "licenseStorage", { save: "function", load: "function" }
    );

    this._storage = storage;
    try {
      this._entries = this._storage.load();
      assert(Array.isArray(this._entries));
    } catch(e) {
      log.warn("eme-persitent-store: could not get entries from license storage", e);
      this.dispose();
    }
  }

  get(initData) {
    initData = hashInitData(initData);
    const entry = _.find(
      this._entries,
      entry => entry.initData === initData
    );
    if (entry) {
      return entry.sessionId;
    }
  }

  add(initData, session) {
    initData = hashInitData(initData);
    if (session.sessionType == "persistent-license" && !this.get(initData)) {
      const sessionId = session.sessionId;
      if (!sessionId) {
        return;
      }

      log.info("eme-persitent-store: add new session", sessionId, session);
      this._entries.push({ sessionId, initData });
      this._save();
    }
  }

  delete(initData) {
    initData = hashInitData(initData);

    const entry = _.find(
      this._entries,
      entry => entry.initData === initData
    );
    if (entry) {
      log.warn("eme-persitent-store: delete session from store", entry);

      const idx = this._entries.indexOf(entry);
      this._entries.splice(idx, 1);
      this._save();
    }
  }

  dispose() {
    this._entries = [];
    this._save();
  }

  _save() {
    try {
      this._storage.save(this._entries);
    } catch(e) {
      log.warn("eme-persitent-store: could not save licenses in localStorage");
    }
  }
}

const emptyStorage = {
  load() { return []; },
  save() {},
};
const $storedSessions = new PersistedSessionsSet(emptyStorage);
const $loadedSessions = new InMemorySessionsSet();

// Persisted singleton instance of MediaKeys. We do not allow multiple
// CDM instances.
let $mediaKeys;
let $mediaKeySystemConfiguration;
let $keySystem;
let $videoElement;

function getCachedKeySystemAccess(keySystems) {
  // NOTE(pierre): alwaysRenew flag is used for IE11 which require the
  // creation of a new MediaKeys instance for each session creation
  if (!$keySystem || !$mediaKeys || $mediaKeys.alwaysRenew) {
    return null;
  }

  const configuration = $mediaKeySystemConfiguration;
  const foundKeySystem = _.find(keySystems, (ks) => {
    if (ks.type !== $keySystem.type) {
      return false;
    }

    if (ks.persistentLicense &&
        configuration.persistentState != "required") {
      return false;
    }

    if (ks.distinctiveIdentifierRequired &&
        configuration.distinctiveIdentifier != "required") {
      return false;
    }

    return true;
  });

  if (foundKeySystem) {
    return {
      keySystem: foundKeySystem,
      keySystemAccess: new KeySystemAccess(
        $keySystem,
        $mediaKeys,
        $mediaKeySystemConfiguration),
    };
  }
  else {
    return null;
  }
}

function buildKeySystemConfiguration(keySystem) {
  const sessionTypes = [];
  let persistentState = "optional";
  let distinctiveIdentifier = "optional";

  if (keySystem.persistentLicense) {
    persistentState = "required";
    sessionTypes.push("persistent-license");
  } else {
    sessionTypes.push("temporary");
  }

  if (keySystem.persistentStateRequired) {
    persistentState = "required";
  }

  if (keySystem.distinctiveIdentifierRequired) {
    distinctiveIdentifier = "required";
  }

  return {
    videoCapabilities: undefined,
    audioCapabilities: undefined,
    initDataTypes: ["cenc"],
    distinctiveIdentifier,
    persistentState,
    sessionTypes,
  };
}<commit_msg>eme: always ask for temporary session type<commit_after>/**
 * Copyright 2015 CANAL+ Group
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const _ = require("canal-js-utils/misc");
const log = require("canal-js-utils/log");
const Promise_ = require("canal-js-utils/promise");
const assert = require("canal-js-utils/assert");
const { Observable } = require("canal-js-utils/rx");
const { combineLatest, empty, fromPromise, merge, just } = Observable;
const {
  KeySystemAccess,
  requestMediaKeySystemAccess,
  setMediaKeys,
  emeEvents,
} = require("./compat");

const {
  onEncrypted,
  onKeyMessage,
  onKeyError,
  onKeyStatusesChange,
} = emeEvents;

const SYSTEMS = {
  "clearkey":  ["webkit-org.w3.clearkey", "org.w3.clearkey"],
  "widevine":  ["com.widevine.alpha"],
  "playready": ["com.youtube.playready", "com.microsoft.playready"]
};

// Key statuses to error mapping. Taken from shaka-player.
const KEY_STATUS_ERRORS = {
  "output-not-allowed": "eme: the required output protection is not available.",
  "expired": "eme: a required key has expired and the content cannot be decrypted.",
  "internal-error": "eme: an unknown error has occurred in the CDM."
};

function hashBuffer(buffer) {
  let hash = 0;
  let char;
  for (let i = 0; i < buffer.length; i++) {
    char = buffer[i];
    hash = ((hash <<  5) - hash) + char;
    hash = hash & hash; // Convert to 32bit integer
  }
  return hash;
}

function hashInitData(initData) {
  if (typeof initData == "number") {
    return initData;
  } else {
    return hashBuffer(initData);
  }
}

const NotSupportedKeySystemError = () =>
  new Error("eme: could not find a compatible key system");

/**
 * Set maintaining a representation of all currently loaded
 * MediaKeySessions. This set allow to reuse sessions without re-
 * negotiating a license exchange if the key is already used in a
 * loaded session.
 */
class InMemorySessionsSet {
  constructor() {
    this._entries = [];
  }

  getFirst() {
    if (this._entries.length > 0) {
      return this._entries[0].session;
    }
  }

  get(initData) {
    initData = hashInitData(initData);
    const entry = _.find(
      this._entries,
      entry => entry.initData === initData
    );
    if (entry) {
      return entry.session;
    }
  }

  add(initData, session) {
    initData = hashInitData(initData);
    if (!this.get(initData)) {
      const sessionId = session.sessionId;
      if (!sessionId) {
        return;
      }

      const entry = { session, initData };
      log.debug("eme-mem-store: add session", entry);
      this._entries.push(entry);
      session.closed.then(() => {
        log.debug("eme-mem-store: remove closed session", entry);
        const idx = this._entries.indexOf(entry);
        if (idx >= 0) {
          this._entries.splice(idx, 1);
        }
      });
    }
  }

  delete(sessionId) {
    const entry = _.find(
      this._entries,
      entry => entry.session.sessionId === sessionId
    );
    if (entry) {
      log.debug("eme-mem-store: delete session", sessionId);
      const idx = this._entries.indexOf(entry);
      this._entries.splice(idx, 1);
      return entry.session;
    }
  }

  deleteAndClose(sessionId) {
    const session = this.delete(sessionId);
    if (session) {
      log.debug("eme-mem-store: close session", sessionId);
      return session.close();
    } else {
      return Promise_.resolve();
    }
  }

  dispose() {
    const disposed = this._entries.map(({ session }) => session.close());
    this._entires = [];
    return Promise_.all(disposed);
  }
}

/**
 * Set representing persisted licenses. Depends on a simple local-
 * storage implementation with a `save`/`load` synchronous interface
 * to persist informations on persisted sessions.
 *
 * This set is used only for a cdm/keysystem with license persistency
 * supported.
 */
class PersistedSessionsSet {
  constructor(storage) {
    this.setStorage(storage);
  }

  setStorage(storage) {
    if (this._storage === storage) {
      return;
    }

    assert(
      storage,
      `eme: no licenseStorage given for keySystem with persistentLicense`
    );

    assert.iface(
      storage,
      "licenseStorage", { save: "function", load: "function" }
    );

    this._storage = storage;
    try {
      this._entries = this._storage.load();
      assert(Array.isArray(this._entries));
    } catch(e) {
      log.warn("eme-persitent-store: could not get entries from license storage", e);
      this.dispose();
    }
  }

  get(initData) {
    initData = hashInitData(initData);
    const entry = _.find(
      this._entries,
      entry => entry.initData === initData
    );
    if (entry) {
      return entry.sessionId;
    }
  }

  add(initData, session) {
    initData = hashInitData(initData);
    if (session.sessionType == "persistent-license" && !this.get(initData)) {
      const sessionId = session.sessionId;
      if (!sessionId) {
        return;
      }

      log.info("eme-persitent-store: add new session", sessionId, session);
      this._entries.push({ sessionId, initData });
      this._save();
    }
  }

  delete(initData) {
    initData = hashInitData(initData);

    const entry = _.find(
      this._entries,
      entry => entry.initData === initData
    );
    if (entry) {
      log.warn("eme-persitent-store: delete session from store", entry);

      const idx = this._entries.indexOf(entry);
      this._entries.splice(idx, 1);
      this._save();
    }
  }

  dispose() {
    this._entries = [];
    this._save();
  }

  _save() {
    try {
      this._storage.save(this._entries);
    } catch(e) {
      log.warn("eme-persitent-store: could not save licenses in localStorage");
    }
  }
}

const emptyStorage = {
  load() { return []; },
  save() {},
};
const $storedSessions = new PersistedSessionsSet(emptyStorage);
const $loadedSessions = new InMemorySessionsSet();

// Persisted singleton instance of MediaKeys. We do not allow multiple
// CDM instances.
let $mediaKeys;
let $mediaKeySystemConfiguration;
let $keySystem;
let $videoElement;

function getCachedKeySystemAccess(keySystems) {
  // NOTE(pierre): alwaysRenew flag is used for IE11 which require the
  // creation of a new MediaKeys instance for each session creation
  if (!$keySystem || !$mediaKeys || $mediaKeys.alwaysRenew) {
    return null;
  }

  const configuration = $mediaKeySystemConfiguration;
  const foundKeySystem = _.find(keySystems, (ks) => {
    if (ks.type !== $keySystem.type) {
      return false;
    }

    if (ks.persistentLicense &&
        configuration.persistentState != "required") {
      return false;
    }

    if (ks.distinctiveIdentifierRequired &&
        configuration.distinctiveIdentifier != "required") {
      return false;
    }

    return true;
  });

  if (foundKeySystem) {
    return {
      keySystem: foundKeySystem,
      keySystemAccess: new KeySystemAccess(
        $keySystem,
        $mediaKeys,
        $mediaKeySystemConfiguration),
    };
  }
  else {
    return null;
  }
}

function buildKeySystemConfiguration(keySystem) {
  const sessionTypes = ["temporary"];
  let persistentState = "optional";
  let distinctiveIdentifier = "optional";

  if (keySystem.persistentLicense) {
    persistentState = "required";
    sessionTypes.push("persistent-license");
  }

  if (keySystem.persistentStateRequired) {
    persistentState = "required";
  }

  if (keySystem.distinctiveIdentifierRequired) {
    distinctiveIdentifier = "required";
  }

  return {
    videoCapabilities: undefined,
    audioCapabilities: undefined,
    initDataTypes: ["cenc"],
    distinctiveIdentifier,
    persistentState,
    sessionTypes,
  };
}
{'fraction_non_alphanumeric': 0.12485428553899013, 'fraction_numerical': 0.0028222590342965826, 'mean_word_length': 3.006882989183874, 'pattern_counts': {'":': 12, '<': 9, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1597274', 'n_tokens_mistral': 4982, 'n_tokens_neox': 4874, 'n_words': 1295}