Source: streaming/utils/EBMLParser.js

import FactoryMaker from '../../core/FactoryMaker';
import Error from '../vo/Error';

/**
 * Creates an instance of an EBMLParser class which implements a large subset
 * of the functionality required to parse Matroska EBML
 *
 * @param {Object} config object with data member which is the buffer to parse
 */
function EBMLParser(config) {

    let instance;

    let data = new DataView(config.data);
    let pos = 0;

    function getPos() {
        return pos;
    }

    function setPos(value) {
        pos = value;
    }

    /**
     * Consumes an EBML tag from the data stream.
     *
     * @param {Object} tag to parse, A tag is an object with at least a {number} tag and
     * {boolean} required flag.
     * @param {boolean} test whether or not the function should throw if a required
     * tag is not found
     * @return {boolean} whether or not the tag was found
     * @throws will throw an exception if a required tag is not found and test
     * param is false or undefined, or if the stream is malformed.
     * @memberof EBMLParser
     */
    function consumeTag(tag, test) {
        let found = true;
        let bytesConsumed = 0;
        let p1;
        let p2;

        if (test === undefined) {
            test = false;
        }

        if (tag.tag > 0xFFFFFF) {
            if (data.getUint32(pos) !== tag.tag) {
                found = false;
            }
            bytesConsumed = 4;
        } else if (tag.tag > 0xFFFF) {
            // 3 bytes
            p1 = data.getUint16(pos);
            p2 = data.getUint8(pos + 2);

            // shift p1 over a byte and add p2
            if (p1 * 256 + p2 !== tag.tag) {
                found = false;
            }
            bytesConsumed = 3;
        } else if (tag.tag > 0xFF) {
            if (data.getUint16(pos) !== tag.tag) {
                found = false;
            }
            bytesConsumed = 2;
        } else {
            if (data.getUint8(pos) !== tag.tag) {
                found = false;
            }
            bytesConsumed = 1;
        }

        if (!found && tag.required && !test) {
            throw new Error('required tag not found');
        }

        if (found) {
            pos += bytesConsumed;
        }

        return found;
    }

    /**
     * Consumes an EBML tag from the data stream.   If the tag is found then this
     * function will also remove the size field which follows the tag from the
     * data stream.
     *
     * @param {Object} tag to parse, A tag is an object with at least a {number} tag and
     * {boolean} required flag.
     * @param {boolean} test whether or not the function should throw if a required
     * tag is not found
     * @return {boolean} whether or not the tag was found
     * @throws will throw an exception if a required tag is not found and test
     * param is false or undefined, or if the stream is malformedata.
     * @memberof EBMLParser
     */
    function consumeTagAndSize(tag, test) {
        let found = consumeTag(tag, test);

        if (found) {
            getMatroskaCodedNum();
        }

        return found;
    }

    /**
     * Consumes an EBML tag from the data stream.   If the tag is found then this
     * function will also remove the size field which follows the tag from the
     * data stream.  It will use the value of the size field to parse a binary
     * field, using a parser defined in the tag itself
     *
     * @param {Object} tag to parse, A tag is an object with at least a {number} tag,
     * {boolean} required flag, and a parse function which takes a size parameter
     * @return {boolean} whether or not the tag was found
     * @throws will throw an exception if a required tag is not found,
     * or if the stream is malformed
     * @memberof EBMLParser
     */
    function parseTag(tag) {
        let size;

        consumeTag(tag);
        size = getMatroskaCodedNum();
        return instance[tag.parse](size);
    }

    /**
     * Consumes an EBML tag from the data stream.   If the tag is found then this
     * function will also remove the size field which follows the tag from the
     * data stream.  It will use the value of the size field to skip over the
     * entire section of EBML encapsulated by the tag.
     *
     * @param {Object} tag to parse, A tag is an object with at least a {number} tag, and
     * {boolean} required flag
     * @param {boolean} test a flag to indicate if an exception should be thrown
     * if a required tag is not found
     * @return {boolean} whether or not the tag was found
     * @throws will throw an exception if a required tag is not found and test is
     * false or undefined or if the stream is malformed
     * @memberof EBMLParser
     */
    function skipOverElement(tag, test) {
        let found = consumeTag(tag, test);
        let headerSize;

        if (found) {
            headerSize = getMatroskaCodedNum();
            pos += headerSize;
        }

        return found;
    }

    /**
     * Returns and consumes a number encoded according to the Matroska EBML
     * specification from the bitstream.
     *
     * @param {boolean} retainMSB whether or not to retain the Most Significant Bit (the
     * first 1). this is usually true when reading Tag IDs.
     * @return {number} the decoded number
     * @throws will throw an exception if the bit stream is malformed or there is
     * not enough data
     * @memberof EBMLParser
     */
    function getMatroskaCodedNum(retainMSB) {
        let bytesUsed = 1;
        let mask = 0x80;
        let maxBytes = 8;
        let extraBytes = -1;
        let num = 0;
        let ch = data.getUint8(pos);
        let i;

        for (i = 0; i < maxBytes; i += 1) {
            if ((ch & mask) === mask) {
                num = (retainMSB === undefined) ? ch & ~mask : ch;
                extraBytes = i;
                break;
            }
            mask >>= 1;
        }

        for (i = 0; i < extraBytes; i += 1, bytesUsed += 1) {
            num = (num << 8) | (0xff & data.getUint8(pos + bytesUsed));
        }

        pos += bytesUsed;

        return num;
    }

    /**
     * Returns and consumes a float from the bitstream.
     *
     * @param {number} size 4 or 8 byte floats are supported
     * @return {number} the decoded number
     * @throws will throw an exception if the bit stream is malformed or there is
     * not enough data
     * @memberof EBMLParser
     */
    function getMatroskaFloat(size) {
        let outFloat;

        switch (size) {
        case 4:
            outFloat = data.getFloat32(pos);
            pos += 4;
            break;
        case 8:
            outFloat = data.getFloat64(pos);
            pos += 8;
            break;
        }
        return outFloat;
    }

    /**
     * Consumes and returns an unsigned int from the bitstream.
     *
     * @param {number} size 1 to 8 bytes
     * @return {number} the decoded number
     * @throws will throw an exception if the bit stream is malformed or there is
     * not enough data
     * @memberof EBMLParser
     */
    function getMatroskaUint(size) {
        let val = 0;
        let i;

        for (i = 0; i < size; i += 1) {
            val <<= 8;
            val |= data.getUint8(pos + i) & 0xff;
        }

        pos += size;
        return val;
    }

    /**
     * Tests whether there is more data in the bitstream for parsing
     *
     * @return {boolean} whether there is more data to parse
     * @memberof EBMLParser
     */
    function moreData() {
        return pos < data.byteLength;
    }

    instance = {
        getPos: getPos,
        setPos: setPos,
        consumeTag: consumeTag,
        consumeTagAndSize: consumeTagAndSize,
        parseTag: parseTag,
        skipOverElement: skipOverElement,
        getMatroskaCodedNum: getMatroskaCodedNum,
        getMatroskaFloat: getMatroskaFloat,
        getMatroskaUint: getMatroskaUint,
        moreData: moreData
    };

    return instance;
}

EBMLParser.__dashjs_factory_name = 'EBMLParser';
export default FactoryMaker.getClassFactory(EBMLParser);