org.cablelabs.playready.cryptfile.PlayReadyPSSH.java Source code

Java tutorial

Introduction

Here is the source code for org.cablelabs.playready.cryptfile.PlayReadyPSSH.java

Source

/* Copyright (c) 2014, CableLabs, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. 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.
 *
 * 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.
 */

package org.cablelabs.playready.cryptfile;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.List;

import org.apache.commons.codec.binary.Base64;
import org.cablelabs.cryptfile.Bitstream;
import org.cablelabs.cryptfile.DRMInfoPSSH;
import org.cablelabs.playready.WRMHeader;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

/**
 * Generates PlayReady-specific PSSH for MP4Box cryptfiles
 */
public class PlayReadyPSSH extends DRMInfoPSSH {

    private static final String MSPRO_ELEMENT = "mspr:pro";

    private static final byte[] PLAYREADY_SYSTEM_ID = { (byte) 0x9a, (byte) 0x04, (byte) 0xf0, (byte) 0x79,
            (byte) 0x98, (byte) 0x40, (byte) 0x42, (byte) 0x86, (byte) 0xab, (byte) 0x92, (byte) 0xe6, (byte) 0x5b,
            (byte) 0xe0, (byte) 0x88, (byte) 0x5f, (byte) 0x95 };

    private List<WRMHeader> wrmHeaders;
    private int wrmHeadersSize = 0;
    private int proSize;
    private ContentProtectionType cpType;

    /**
     * License server URL to use for testing
     */
    public static final String TEST_URL = "http://playready.directtaps.net/pr/svc/rightsmanager.asmx?PlayRight=1&UseSimpleNonPersistentLicense=1";

    /**
     * Two different types of ContentProtection elements can be generated base on
     * the PSSH data.  One is a custom Microsoft format (MSPRO) and the other
     * is a standard CommonEncryption format (CENC)
     */
    public enum ContentProtectionType {
        MSPRO, CENC
    };

    /**
     * Returns whether or not the given systemID is PlayReady
     * 
     * @param systemID the systemID to check
     * @return true if the systemID is PlayReady, false otherwise
     */
    public static boolean isPlayReady(byte[] systemID) {
        return systemIDMatch(PLAYREADY_SYSTEM_ID, systemID);
    }

    public PlayReadyPSSH(List<WRMHeader> wrmHeaders, ContentProtectionType cpType) {
        super(PLAYREADY_SYSTEM_ID);
        this.wrmHeaders = wrmHeaders;
        this.cpType = cpType;

        // Collect all of our WRMHeader data arrays so that we can calculate the
        // total size
        for (WRMHeader header : wrmHeaders) {
            wrmHeadersSize += header.getWRMHeaderData().length;
        }

        // Total size of PlayReady Header Object is:
        // 
        //    4          PlayReady Header Object Size field
        //    2          Number of Records field
        //    4*NumRec   Record Type and Record Length field for each record
        //    RecSize    Size of all headers
        proSize = 4 + 2 + (4 * wrmHeaders.size()) + wrmHeadersSize;
    }

    @Override
    public Element generateContentProtection(Document d) throws IOException {
        Element e = super.generateContentProtection(d);

        switch (cpType) {

        case CENC:
            e.appendChild(generateCENCContentProtectionData(d));
            break;

        case MSPRO:
            Element pro = d.createElement(MSPRO_ELEMENT);

            // Generate base64-encoded PRO
            ByteBuffer ba = ByteBuffer.allocate(4);
            ba.order(ByteOrder.LITTLE_ENDIAN);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            DataOutputStream dos = new DataOutputStream(baos);

            // PlayReady Header Object Size field
            ba.putInt(0, proSize);
            dos.write(ba.array());

            // Number of Records field
            ba.putShort(0, (short) wrmHeaders.size());
            dos.write(ba.array(), 0, 2);

            for (WRMHeader header : wrmHeaders) {

                byte[] wrmData = header.getWRMHeaderData();

                // Record Type (always 1 for WRM Headers)
                ba.putShort(0, (short) 1);
                dos.write(ba.array(), 0, 2);

                // Record Length
                ba.putShort(0, (short) wrmData.length);
                dos.write(ba.array(), 0, 2);

                // Data
                dos.write(wrmData);
            }

            pro.setTextContent(Base64.encodeBase64String(baos.toByteArray()));

            e.appendChild(pro);
            break;
        }

        return e;
    }

    // Helper to convert ints of varying widths (in bytes), to a little endian byte array
    private byte[] toLEBytes(int input, int width) {
        byte[] retVal = new byte[width];
        for (int i = 0; i < width; i++) {
            retVal[i] = (byte) ((input >> (i * 8)) & 0xFF);
        }
        return retVal;
    }

    @Override
    protected void generatePSSHData(DataOutputStream dos) throws IOException {

        // PlayReady Header Object Size field
        dos.write(toLEBytes(proSize, 4));

        // Number of Records field
        dos.write(toLEBytes(wrmHeaders.size(), 2));

        for (WRMHeader header : wrmHeaders) {

            byte[] wrmData = header.getWRMHeaderData();

            // Record Type (always 1 for WRM Headers)
            dos.write(toLEBytes(1, 2));

            // Record Length
            dos.write(toLEBytes(wrmData.length, 2));

            // Data
            dos.write(wrmData);
        }
    }

    /*
     * (non-Javadoc)
     * @see org.cablelabs.cryptfile.MP4BoxXML#generateXML(org.w3c.dom.Document)
     */
    @Override
    public Node generateXML(Document d) {

        Element e = generateDRMInfo(d);
        Bitstream b = new Bitstream();

        // PlayReady Header Object Size field
        b.setupIntegerLE(proSize, 32);
        e.appendChild(b.generateXML(d));

        // Number of Records field
        b.setupIntegerLE(wrmHeaders.size(), 16);
        e.appendChild(b.generateXML(d));

        for (WRMHeader header : wrmHeaders) {

            byte[] wrmData = header.getWRMHeaderData();

            // Record Type (always 1 for WRM Headers)
            b.setupIntegerLE(1, 16);
            e.appendChild(b.generateXML(d));

            // Record Length
            b.setupIntegerLE(wrmData.length, 16);
            e.appendChild(b.generateXML(d));

            // Data
            b.setupDataB64(wrmData);
            e.appendChild(b.generateXML(d));
        }

        return e;
    }
}