Source: lib/util/data_view_reader.js

/**
 * @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.DataViewReader');

goog.require('goog.asserts');
goog.require('shaka.util.Error');
goog.require('shaka.util.StringUtils');



/**
 * Creates a DataViewReader, which abstracts a DataView object.
 *
 * @param {!DataView} dataView The DataView.
 * @param {shaka.util.DataViewReader.Endianness} endianness The endianness.
 *
 * @struct
 * @constructor
 * @export
 */
shaka.util.DataViewReader = function(dataView, endianness) {
  /** @private {!DataView} */
  this.dataView_ = dataView;

  /** @private {boolean} */
  this.littleEndian_ =
      endianness == shaka.util.DataViewReader.Endianness.LITTLE_ENDIAN;

  /** @private {number} */
  this.position_ = 0;
};


/**
 * Endianness.
 * @enum {number}
 * @export
 */
shaka.util.DataViewReader.Endianness = {
  BIG_ENDIAN: 0,
  LITTLE_ENDIAN: 1
};


/**
 * @return {boolean} True if the reader has more data, false otherwise.
 * @export
 */
shaka.util.DataViewReader.prototype.hasMoreData = function() {
  return this.position_ < this.dataView_.byteLength;
};


/**
 * Gets the current byte position.
 * @return {number}
 * @export
 */
shaka.util.DataViewReader.prototype.getPosition = function() {
  return this.position_;
};


/**
 * Gets the byte length of the DataView.
 * @return {number}
 * @export
 */
shaka.util.DataViewReader.prototype.getLength = function() {
  return this.dataView_.byteLength;
};


/**
 * Reads an unsigned 8 bit integer, and advances the reader.
 * @return {number} The integer.
 * @throws {shaka.util.Error} when reading past the end of the data view.
 * @export
 */
shaka.util.DataViewReader.prototype.readUint8 = function() {
  try {
    let value = this.dataView_.getUint8(this.position_);
    this.position_ += 1;
    return value;
  } catch (exception) {
    this.throwOutOfBounds_();
  }
};


/**
 * Reads an unsigned 16 bit integer, and advances the reader.
 * @return {number} The integer.
 * @throws {shaka.util.Error} when reading past the end of the data view.
 * @export
 */
shaka.util.DataViewReader.prototype.readUint16 = function() {
  try {
    let value = this.dataView_.getUint16(this.position_, this.littleEndian_);
    this.position_ += 2;
    return value;
  } catch (exception) {
    this.throwOutOfBounds_();
  }
};


/**
 * Reads an unsigned 32 bit integer, and advances the reader.
 * @return {number} The integer.
 * @throws {shaka.util.Error} when reading past the end of the data view.
 * @export
 */
shaka.util.DataViewReader.prototype.readUint32 = function() {
  try {
    let value = this.dataView_.getUint32(this.position_, this.littleEndian_);
    this.position_ += 4;
    return value;
  } catch (exception) {
    this.throwOutOfBounds_();
  }
};


/**
 * Reads a signed 32 bit integer, and advances the reader.
 * @return {number} The integer.
 * @throws {shaka.util.Error} when reading past the end of the data view.
 * @export
 */
shaka.util.DataViewReader.prototype.readInt32 = function() {
  try {
    let value = this.dataView_.getInt32(this.position_, this.littleEndian_);
    this.position_ += 4;
    return value;
  } catch (exception) {
    this.throwOutOfBounds_();
  }
};


/**
 * Reads an unsigned 64 bit integer, and advances the reader.
 * @return {number} The integer.
 * @throws {shaka.util.Error} when reading past the end of the data view or
 *   when reading an integer too large to store accurately in JavaScript.
 * @export
 */
shaka.util.DataViewReader.prototype.readUint64 = function() {
  let low, high;

  try {
    if (this.littleEndian_) {
      low = this.dataView_.getUint32(this.position_, true);
      high = this.dataView_.getUint32(this.position_ + 4, true);
    } else {
      high = this.dataView_.getUint32(this.position_, false);
      low = this.dataView_.getUint32(this.position_ + 4, false);
    }
  } catch (exception) {
    this.throwOutOfBounds_();
  }

  if (high > 0x1FFFFF) {
    throw new shaka.util.Error(
        shaka.util.Error.Severity.CRITICAL,
        shaka.util.Error.Category.MEDIA,
        shaka.util.Error.Code.JS_INTEGER_OVERFLOW);
  }

  this.position_ += 8;

  // NOTE: This is subtle, but in JavaScript you can't shift left by 32 and get
  // the full range of 53-bit values possible.  You must multiply by 2^32.
  return (high * Math.pow(2, 32)) + low;
};


/**
 * Reads the specified number of raw bytes.
 * @param {number} bytes The number of bytes to read.
 * @return {!Uint8Array}
 * @throws {shaka.util.Error} when reading past the end of the data view.
 * @export
 */
shaka.util.DataViewReader.prototype.readBytes = function(bytes) {
  goog.asserts.assert(bytes > 0, 'Bad call to DataViewReader.readBytes');
  if (this.position_ + bytes > this.dataView_.byteLength) {
    this.throwOutOfBounds_();
  }

  let value = new Uint8Array(
      this.dataView_.buffer, this.dataView_.byteOffset + this.position_, bytes);
  this.position_ += bytes;
  return new Uint8Array(value);
};


/**
 * Skips the specified number of bytes.
 * @param {number} bytes The number of bytes to skip.
 * @throws {shaka.util.Error} when skipping past the end of the data view.
 * @export
 */
shaka.util.DataViewReader.prototype.skip = function(bytes) {
  goog.asserts.assert(bytes >= 0, 'Bad call to DataViewReader.skip');
  if (this.position_ + bytes > this.dataView_.byteLength) {
    this.throwOutOfBounds_();
  }
  this.position_ += bytes;
};


/**
 * Rewinds the specified number of bytes.
 * @param {number} bytes The number of bytes to rewind.
 * @throws {shaka.util.Error} when rewinding past the beginning of the data
 *   view.
 * @export
 */
shaka.util.DataViewReader.prototype.rewind = function(bytes) {
  goog.asserts.assert(bytes >= 0, 'Bad call to DataViewReader.rewind');
  if (this.position_ < bytes) {
    this.throwOutOfBounds_();
  }
  this.position_ -= bytes;
};


/**
 * Seeks to a specified position.
 * @param {number} position The desired byte position within the DataView.
 * @throws {shaka.util.Error} when seeking outside the range of the data view.
 * @export
 */
shaka.util.DataViewReader.prototype.seek = function(position) {
  goog.asserts.assert(position >= 0, 'Bad call to DataViewReader.seek');
  if (position < 0 || position > this.dataView_.byteLength) {
    this.throwOutOfBounds_();
  }
  this.position_ = position;
};


/**
 * Keeps reading until it reaches a byte that equals to zero.  The text is
 * assumed to be UTF-8.
 * @return {string}
 * @export
 */
shaka.util.DataViewReader.prototype.readTerminatedString = function() {
  let start = this.position_;
  while (this.hasMoreData()) {
    let value = this.dataView_.getUint8(this.position_);
    if (value == 0) break;
    this.position_ += 1;
  }

  let ret = new Uint8Array(
      this.dataView_.buffer, this.dataView_.byteOffset + start,
      this.position_ - start);
  // Skip string termination.
  this.position_ += 1;
  return shaka.util.StringUtils.fromUTF8(ret);
};


/**
 * @throws {shaka.util.Error}
 * @private
 */
shaka.util.DataViewReader.prototype.throwOutOfBounds_ = function() {
  throw new shaka.util.Error(
      shaka.util.Error.Severity.CRITICAL,
      shaka.util.Error.Category.MEDIA,
      shaka.util.Error.Code.BUFFER_READ_OUT_OF_BOUNDS);
};