# Source: streaming/protection/drm/KeySystemPlayReady.js

    /**
     * The copyright in this software is being made available under the BSD License,
     * included below. This software may be subject to other third party and contributor
     * rights, including patent rights, and no such rights are granted under this license.
     *
     * Copyright (c) 2013, Dash Industry Forum.
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without modification,
     * are permitted provided that the following conditions are met:
     *  * Redistributions of source code must retain the above copyright notice, this
     *  list of conditions and the following disclaimer.
     *  * Redistributions in binary form must reproduce the above copyright notice,
     *  this list of conditions and the following disclaimer in the documentation and/or
     *  other materials provided with the distribution.
     *  * Neither the name of Dash Industry Forum nor the names of its
     *  contributors may be used to endorse or promote products derived from this software
     *  without specific prior written permission.
     *
     *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY
     *  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     *  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     *  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
     *  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
     *  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
     *  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     *  POSSIBILITY OF SUCH DAMAGE.
     */

    /**
     * Microsoft PlayReady DRM
     *
     * @class
     * @implements KeySystem
     */
    import CommonEncryption from '../CommonEncryption';
    import ProtectionConstants from '../../constants/ProtectionConstants';

    const uuid = '9a04f079-9840-4286-ab92-e65be0885f95';
    const systemString = ProtectionConstants.PLAYREADY_KEYSTEM_STRING;
    const schemeIdURI = 'urn:uuid:' + uuid;
    const PRCDMData = '<PlayReadyCDMData type="LicenseAcquisition"><LicenseAcquisition version="1.0" Proactive="false"><CustomData encoding="base64encoded">%CUSTOMDATA%</CustomData></LicenseAcquisition></PlayReadyCDMData>';

    function KeySystemPlayReady(config) {

        config = config || {};
        let instance;
        let messageFormat = 'utf-16';
        const BASE64 = config.BASE64;
        const settings = config.settings;

        function checkConfig() {
            if (!BASE64 || !BASE64.hasOwnProperty('decodeArray') || !BASE64.hasOwnProperty('decodeArray') ) {
                throw new Error('Missing config parameter(s)');
            }
        }

        function getRequestHeadersFromMessage(message) {
            let msg,
                xmlDoc;
            const headers = {};
            const parser = new DOMParser();

            if (settings && settings.get().streaming.protection.detectPlayreadyMessageFormat) {
                // If message format configured/defaulted to utf-16 AND number of bytes is odd, assume 'unwrapped' raw CDM message.
                if (messageFormat === 'utf-16' && message && message.byteLength % 2 === 1) {
                    headers['Content-Type'] = 'text/xml; charset=utf-8';
                    return headers;
                }
            }

            const dataview = (messageFormat === 'utf-16') ? new Uint16Array(message) : new Uint8Array(message);

            msg = String.fromCharCode.apply(null, dataview);
            xmlDoc = parser.parseFromString(msg, 'application/xml');

            const headerNameList = xmlDoc.getElementsByTagName('name');
            const headerValueList = xmlDoc.getElementsByTagName('value');
            for (let i = 0; i < headerNameList.length; i++) {
                headers[headerNameList[i].childNodes[0].nodeValue] = headerValueList[i].childNodes[0].nodeValue;
            }
            // Some versions of the PlayReady CDM return 'Content' instead of 'Content-Type'.
            // this is NOT w3c conform and license servers may reject the request!
            // -> rename it to proper w3c definition!
            if (headers.hasOwnProperty('Content')) {
                headers['Content-Type'] = headers.Content;
                delete headers.Content;
            }
            // Set Content-Type header by default if not provided in the the CDM message (<PlayReadyKeyMessage/>)
            // or if the message contains directly the challenge itself (Ex: LG SmartTVs)
            if (!headers.hasOwnProperty('Content-Type')) {
                headers['Content-Type'] = 'text/xml; charset=utf-8';
            }
            return headers;
        }

        function getLicenseRequestFromMessage(message) {
            let licenseRequest = null;
            const parser = new DOMParser();

            if (settings && settings.get().streaming.protection.detectPlayreadyMessageFormat) {
                // If message format configured/defaulted to utf-16 AND number of bytes is odd, assume 'unwrapped' raw CDM message.
                if (messageFormat === 'utf-16' && message && message.byteLength % 2 === 1) {
                    return message;
                }
            }

            const dataview = (messageFormat === 'utf-16') ? new Uint16Array(message) : new Uint8Array(message);

            checkConfig();
            const msg = String.fromCharCode.apply(null, dataview);
            const xmlDoc = parser.parseFromString(msg, 'application/xml');

            if (xmlDoc.getElementsByTagName('PlayReadyKeyMessage')[0]) {
                const Challenge = xmlDoc.getElementsByTagName('Challenge')[0].childNodes[0].nodeValue;
                if (Challenge) {
                    licenseRequest = BASE64.decode(Challenge);
                }
            } else {
                // The message from CDM is not a wrapped message as on IE11 and Edge,
                // thus it contains direclty the challenge itself
                // (note that the xmlDoc at this point may be unreadable since it may have been interpreted as UTF-16)
                return message;
            }

            return licenseRequest;
        }

        function getLicenseServerURLFromInitData(initData) {
            if (initData) {
                const data = new DataView(initData);
                const numRecords = data.getUint16(4, true);
                let offset = 6;
                const parser = new DOMParser();

                for (let i = 0; i < numRecords; i++) {
                    // Parse the PlayReady Record header
                    const recordType = data.getUint16(offset, true);
                    offset += 2;
                    const recordLength = data.getUint16(offset, true);
                    offset += 2;
                    if (recordType !== 0x0001) {
                        offset += recordLength;
                        continue;
                    }

                    const recordData = initData.slice(offset, offset + recordLength);
                    const record = String.fromCharCode.apply(null, new Uint16Array(recordData));
                    const xmlDoc = parser.parseFromString(record, 'application/xml');

                    // First try <LA_URL>
                    if (xmlDoc.getElementsByTagName('LA_URL')[0]) {
                        const laurl = xmlDoc.getElementsByTagName('LA_URL')[0].childNodes[0].nodeValue;
                        if (laurl) {
                            return laurl;
                        }
                    }

                    // Optionally, try <LUI_URL>
                    if (xmlDoc.getElementsByTagName('LUI_URL')[0]) {
                        const luiurl = xmlDoc.getElementsByTagName('LUI_URL')[0].childNodes[0].nodeValue;
                        if (luiurl) {
                            return luiurl;
                        }
                    }
                }
            }

            return null;
        }

        function getInitData(cpData) {
            // * desc@ getInitData
            // *   generate PSSH data from PROHeader defined in MPD file
            // *   PSSH format:
            // *   size (4)
            // *   box type(PSSH) (8)
            // *   Protection SystemID (16)
            // *   protection system data size (4) - length of decoded PROHeader
            // *   decoded PROHeader data from MPD file
            const PSSHBoxType = new Uint8Array([0x70, 0x73, 0x73, 0x68, 0x00, 0x00, 0x00, 0x00]); //'PSSH' 8 bytes
            const playreadySystemID = new Uint8Array([0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86, 0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95]);

            let byteCursor = 0;
            let uint8arraydecodedPROHeader = null;

            let PROSize,
                PSSHSize,
                PSSHBoxBuffer,
                PSSHBox,
                PSSHData;

            checkConfig();
            if (!cpData) {
                return null;
            }
            // Handle common encryption PSSH
            if ('pssh' in cpData) {
                return CommonEncryption.parseInitDataFromContentProtection(cpData, BASE64);
            }
            // Handle native MS PlayReady ContentProtection elements
            if ('pro' in cpData) {
                uint8arraydecodedPROHeader = BASE64.decodeArray(cpData.pro.__text);
            }
            else if ('prheader' in cpData) {
                uint8arraydecodedPROHeader = BASE64.decodeArray(cpData.prheader.__text);
            }
            else {
                return null;
            }

            PROSize = uint8arraydecodedPROHeader.length;
            PSSHSize = 0x4 + PSSHBoxType.length + playreadySystemID.length + 0x4 + PROSize;

            PSSHBoxBuffer = new ArrayBuffer(PSSHSize);

            PSSHBox = new Uint8Array(PSSHBoxBuffer);
            PSSHData = new DataView(PSSHBoxBuffer);

            PSSHData.setUint32(byteCursor, PSSHSize);
            byteCursor += 0x4;

            PSSHBox.set(PSSHBoxType, byteCursor);
            byteCursor += PSSHBoxType.length;

            PSSHBox.set(playreadySystemID, byteCursor);
            byteCursor += playreadySystemID.length;

            PSSHData.setUint32(byteCursor, PROSize);
            byteCursor += 0x4;

            PSSHBox.set(uint8arraydecodedPROHeader, byteCursor);
            byteCursor += PROSize;

            return PSSHBox.buffer;
        }

        /**
         * It seems that some PlayReady implementations return their XML-based CDM
         * messages using UTF16, while others return them as UTF8.  Use this function
         * to modify the message format to expect when parsing CDM messages.
         *
         * @param {string} format the expected message format.  Either "utf-8" or "utf-16".
         * @throws {Error} Specified message format is not one of "utf8" or "utf16"
         */
        function setPlayReadyMessageFormat(format) {
            if (format !== 'utf-8' && format !== 'utf-16') {
                throw new Error('Specified message format is not one of "utf-8" or "utf-16"');
            }
            messageFormat = format;
        }

        /**
         * Get Playready Custom data
         */
        function getCDMData(_cdmData) {
            let customData,
                cdmData,
                cdmDataBytes,
                i;

            checkConfig();
            if (!_cdmData) return null;

            // Convert custom data into multibyte string
            customData = [];
            for (i = 0; i < _cdmData.length; ++i) {
                customData.push(_cdmData.charCodeAt(i));
                customData.push(0);
            }
            customData = String.fromCharCode.apply(null, customData);

            // Encode in Base 64 the custom data string
            customData = BASE64.encode(customData);

            // Initialize CDM data with Base 64 encoded custom data
            // (see https://msdn.microsoft.com/en-us/library/dn457361.aspx)
            cdmData = PRCDMData.replace('%CUSTOMDATA%', customData);

            // Convert CDM data into multibyte characters
            cdmDataBytes = [];
            for (i = 0; i < cdmData.length; ++i) {
                cdmDataBytes.push(cdmData.charCodeAt(i));
                cdmDataBytes.push(0);
            }

            return new Uint8Array(cdmDataBytes).buffer;
        }

        instance = {
            uuid,
            schemeIdURI,
            systemString,
            getInitData,
            getRequestHeadersFromMessage,
            getLicenseRequestFromMessage,
            getLicenseServerURLFromInitData,
            getCDMData,
            setPlayReadyMessageFormat
        };

        return instance;
    }

    KeySystemPlayReady.__dashjs_factory_name = 'KeySystemPlayReady';
    export default dashjs.FactoryMaker.getSingletonFactory(KeySystemPlayReady); /* jshint ignore:line */

#### Search results