/** * @license * Copyright 2016 Google Inc. * * 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. */ goog.provide('shaka.util.EbmlElement'); goog.provide('shaka.util.EbmlParser'); goog.require('goog.asserts'); goog.require('shaka.util.DataViewReader'); goog.require('shaka.util.Error'); goog.require('shaka.util.Uint8ArrayUtils'); /** * @summary * Extensible Binary Markup Language (EBML) parser. */ shaka.util.EbmlParser = class { /** * @param {!DataView} dataView The EBML data. */ constructor(dataView) { /** @private {!DataView} */ this.dataView_ = dataView; /** @private {!shaka.util.DataViewReader} */ this.reader_ = new shaka.util.DataViewReader( dataView, shaka.util.DataViewReader.Endianness.BIG_ENDIAN); } /** * @return {boolean} True if the parser has more data, false otherwise. */ hasMoreData() { return this.reader_.hasMoreData(); } /** * Parses an EBML element from the parser's current position, and advances * the parser. * @return {!shaka.util.EbmlElement} The EBML element. * @throws {shaka.util.Error} * @see http://matroska.org/technical/specs/rfc/index.html */ parseElement() { const id = this.parseId_(); // Parse the element's size. const vint = this.parseVint_(); let size; if (shaka.util.EbmlParser.isDynamicSizeValue_(vint)) { // If this has an unknown size, assume that it takes up the rest of the // data. size = this.dataView_.byteLength - this.reader_.getPosition(); } else { size = shaka.util.EbmlParser.getVintValue_(vint); } // Note that if the element's size is larger than the buffer then we are // parsing a "partial element". This may occur if for example we are // parsing the beginning of some WebM container data, but our buffer does // not contain the entire WebM container data. const elementSize = this.reader_.getPosition() + size <= this.dataView_.byteLength ? size : this.dataView_.byteLength - this.reader_.getPosition(); const dataView = new DataView( this.dataView_.buffer, this.dataView_.byteOffset + this.reader_.getPosition(), elementSize); this.reader_.skip(elementSize); return new shaka.util.EbmlElement(id, dataView); } /** * Parses an EBML ID from the parser's current position, and advances the * parser. * @throws {shaka.util.Error} * @return {number} The EBML ID. * @private */ parseId_() { const vint = this.parseVint_(); if (vint.length > 7) { throw new shaka.util.Error( shaka.util.Error.Severity.CRITICAL, shaka.util.Error.Category.MEDIA, shaka.util.Error.Code.EBML_OVERFLOW); } let id = 0; for (const /* byte */ b of vint) { // Note that we cannot use << since |value| may exceed 32 bits. id = (256 * id) + b; } return id; } /** * Parses a variable sized integer from the parser's current position, and * advances the parser. * For example: * 1 byte wide: 1xxx xxxx * 2 bytes wide: 01xx xxxx xxxx xxxx * 3 bytes wide: 001x xxxx xxxx xxxx xxxx xxxx * @return {!Uint8Array} The variable sized integer. * @private */ parseVint_() { const firstByte = this.reader_.readUint8(); if (firstByte == 0) { throw new shaka.util.Error( shaka.util.Error.Severity.CRITICAL, shaka.util.Error.Category.MEDIA, shaka.util.Error.Code.EBML_OVERFLOW); } // Determine the index of the highest bit set. const index = Math.floor(Math.log2(firstByte)); const numBytes = 8 - index; goog.asserts.assert(numBytes <= 8 && numBytes >= 1, 'Incorrect log2 value'); const vint = new Uint8Array(numBytes); vint[0] = firstByte; // Include the remaining bytes. for (let i = 1; i < numBytes; i++) { vint[i] = this.reader_.readUint8(); } return vint; } /** * Gets the value of a variable sized integer. * For example, the x's below are part of the vint's value. * 7-bit value: 1xxx xxxx * 14-bit value: 01xx xxxx xxxx xxxx * 21-bit value: 001x xxxx xxxx xxxx xxxx xxxx * @param {!Uint8Array} vint The variable sized integer. * @throws {shaka.util.Error} * @return {number} The value of the variable sized integer. * @private */ static getVintValue_(vint) { // If |vint| is 8 bytes wide then we must ensure that it does not have more // than 53 meaningful bits. For example, assume |vint| is 8 bytes wide, // so it has the following structure, // 0000 0001 | xxxx xxxx ... // Thus, the first 3 bits following the first byte of |vint| must be 0. if ((vint.length == 8) && (vint[1] & 0xe0)) { throw new shaka.util.Error( shaka.util.Error.Severity.CRITICAL, shaka.util.Error.Category.MEDIA, shaka.util.Error.Code.JS_INTEGER_OVERFLOW); } // Mask out the first few bits of |vint|'s first byte to get the most // significant bits of |vint|'s value. If |vint| is 8 bytes wide then // |value| will be set to 0. const mask = 0x1 << (8 - vint.length); let value = vint[0] & (mask - 1); // Add the remaining bytes. for (let i = 1; i < vint.length; i++) { // Note that we cannot use << since |value| may exceed 32 bits. value = (256 * value) + vint[i]; } return value; } /** * Checks if the given variable sized integer represents a dynamic size value. * @param {!Uint8Array} vint The variable sized integer. * @return {boolean} true if |vint| represents a dynamic size value, * false otherwise. * @private */ static isDynamicSizeValue_(vint) { const EbmlParser = shaka.util.EbmlParser; const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils; for (const dynamicSizeConst of EbmlParser.DYNAMIC_SIZES) { if (Uint8ArrayUtils.equal(vint, dynamicSizeConst)) { return true; } } return false; } }; /** * A list of EBML dynamic size constants. * @const {!Array.>} */ shaka.util.EbmlParser.DYNAMIC_SIZES = [ [0xff], [0x7f, 0xff], [0x3f, 0xff, 0xff], [0x1f, 0xff, 0xff, 0xff], [0x0f, 0xff, 0xff, 0xff, 0xff], [0x07, 0xff, 0xff, 0xff, 0xff, 0xff], [0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], [0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], ]; shaka.util.EbmlElement = class { /** * @param {number} id The ID. * @param {!DataView} dataView The DataView. */ constructor(id, dataView) { /** @type {number} */ this.id = id; /** @private {!DataView} */ this.dataView_ = dataView; } /** * Gets the element's offset from the beginning of the buffer. * @return {number} */ getOffset() { return this.dataView_.byteOffset; } /** * Interpret the element's data as a list of sub-elements. * @throws {shaka.util.Error} * @return {!shaka.util.EbmlParser} A parser over the sub-elements. */ createParser() { return new shaka.util.EbmlParser(this.dataView_); } /** * Interpret the element's data as an unsigned integer. * @throws {shaka.util.Error} * @return {number} */ getUint() { if (this.dataView_.byteLength > 8) { throw new shaka.util.Error( shaka.util.Error.Severity.CRITICAL, shaka.util.Error.Category.MEDIA, shaka.util.Error.Code.EBML_OVERFLOW); } // Ensure we have at most 53 meaningful bits. if ((this.dataView_.byteLength == 8) && (this.dataView_.getUint8(0) & 0xe0)) { throw new shaka.util.Error( shaka.util.Error.Severity.CRITICAL, shaka.util.Error.Category.MEDIA, shaka.util.Error.Code.JS_INTEGER_OVERFLOW); } let value = 0; for (let i = 0; i < this.dataView_.byteLength; i++) { const chunk = this.dataView_.getUint8(i); value = (256 * value) + chunk; } return value; } /** * Interpret the element's data as a floating point number * (32 bits or 64 bits). 80-bit floating point numbers are not supported. * @throws {shaka.util.Error} * @return {number} */ getFloat() { if (this.dataView_.byteLength == 4) { return this.dataView_.getFloat32(0); } else if (this.dataView_.byteLength == 8) { return this.dataView_.getFloat64(0); } else { throw new shaka.util.Error( shaka.util.Error.Severity.CRITICAL, shaka.util.Error.Category.MEDIA, shaka.util.Error.Code.EBML_BAD_FLOATING_POINT_SIZE); } } };