Source: lib/dash/ebml_parser.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.util.EbmlElement');
  7. goog.provide('shaka.dash.EbmlParser');
  8. goog.require('goog.asserts');
  9. goog.require('shaka.util.BufferUtils');
  10. goog.require('shaka.util.DataViewReader');
  11. goog.require('shaka.util.Error');
  12. /**
  13. * @summary
  14. * Extensible Binary Markup Language (EBML) parser.
  15. */
  16. shaka.dash.EbmlParser = class {
  17. /**
  18. * @param {BufferSource} data
  19. */
  20. constructor(data) {
  21. /** @private {!DataView} */
  22. this.dataView_ = shaka.util.BufferUtils.toDataView(data);
  23. /** @private {!shaka.util.DataViewReader} */
  24. this.reader_ = new shaka.util.DataViewReader(
  25. this.dataView_, shaka.util.DataViewReader.Endianness.BIG_ENDIAN);
  26. }
  27. /**
  28. * @return {boolean} True if the parser has more data, false otherwise.
  29. */
  30. hasMoreData() {
  31. return this.reader_.hasMoreData();
  32. }
  33. /**
  34. * Parses an EBML element from the parser's current position, and advances
  35. * the parser.
  36. * @return {!shaka.util.EbmlElement} The EBML element.
  37. * @see http://matroska.org/technical/specs/rfc/index.html
  38. */
  39. parseElement() {
  40. const id = this.parseId_();
  41. // Parse the element's size.
  42. const vint = this.parseVint_();
  43. let size;
  44. if (shaka.dash.EbmlParser.isDynamicSizeValue_(vint)) {
  45. // If this has an unknown size, assume that it takes up the rest of the
  46. // data.
  47. size = this.dataView_.byteLength - this.reader_.getPosition();
  48. } else {
  49. size = shaka.dash.EbmlParser.getVintValue_(vint);
  50. }
  51. // Note that if the element's size is larger than the buffer then we are
  52. // parsing a "partial element". This may occur if for example we are
  53. // parsing the beginning of some WebM container data, but our buffer does
  54. // not contain the entire WebM container data.
  55. const elementSize =
  56. this.reader_.getPosition() + size <= this.dataView_.byteLength ?
  57. size :
  58. this.dataView_.byteLength - this.reader_.getPosition();
  59. const dataView = shaka.util.BufferUtils.toDataView(
  60. this.dataView_, this.reader_.getPosition(), elementSize);
  61. this.reader_.skip(elementSize);
  62. return new shaka.util.EbmlElement(id, dataView);
  63. }
  64. /**
  65. * Parses an EBML ID from the parser's current position, and advances the
  66. * parser.
  67. * @return {number} The EBML ID.
  68. * @private
  69. */
  70. parseId_() {
  71. const vint = this.parseVint_();
  72. if (vint.length > 7) {
  73. throw new shaka.util.Error(
  74. shaka.util.Error.Severity.CRITICAL,
  75. shaka.util.Error.Category.MEDIA,
  76. shaka.util.Error.Code.EBML_OVERFLOW);
  77. }
  78. let id = 0;
  79. for (const /* byte */ b of vint) {
  80. // Note that we cannot use << since |value| may exceed 32 bits.
  81. id = (256 * id) + b;
  82. }
  83. return id;
  84. }
  85. /**
  86. * Parses a variable sized integer from the parser's current position, and
  87. * advances the parser.
  88. * For example:
  89. * 1 byte wide: 1xxx xxxx
  90. * 2 bytes wide: 01xx xxxx xxxx xxxx
  91. * 3 bytes wide: 001x xxxx xxxx xxxx xxxx xxxx
  92. * @return {!Uint8Array} The variable sized integer.
  93. * @private
  94. */
  95. parseVint_() {
  96. const position = this.reader_.getPosition();
  97. const firstByte = this.reader_.readUint8();
  98. if (firstByte == 0) {
  99. throw new shaka.util.Error(
  100. shaka.util.Error.Severity.CRITICAL,
  101. shaka.util.Error.Category.MEDIA,
  102. shaka.util.Error.Code.EBML_OVERFLOW);
  103. }
  104. // Determine the index of the highest bit set.
  105. const index = Math.floor(Math.log2(firstByte));
  106. const numBytes = 8 - index;
  107. goog.asserts.assert(numBytes <= 8 && numBytes >= 1, 'Incorrect log2 value');
  108. this.reader_.skip(numBytes - 1);
  109. return shaka.util.BufferUtils.toUint8(this.dataView_, position, numBytes);
  110. }
  111. /**
  112. * Gets the value of a variable sized integer.
  113. * For example, the x's below are part of the vint's value.
  114. * 7-bit value: 1xxx xxxx
  115. * 14-bit value: 01xx xxxx xxxx xxxx
  116. * 21-bit value: 001x xxxx xxxx xxxx xxxx xxxx
  117. * @param {!Uint8Array} vint The variable sized integer.
  118. * @return {number} The value of the variable sized integer.
  119. * @private
  120. */
  121. static getVintValue_(vint) {
  122. // If |vint| is 8 bytes wide then we must ensure that it does not have more
  123. // than 53 meaningful bits. For example, assume |vint| is 8 bytes wide,
  124. // so it has the following structure,
  125. // 0000 0001 | xxxx xxxx ...
  126. // Thus, the first 3 bits following the first byte of |vint| must be 0.
  127. if ((vint.length == 8) && (vint[1] & 0xe0)) {
  128. throw new shaka.util.Error(
  129. shaka.util.Error.Severity.CRITICAL,
  130. shaka.util.Error.Category.MEDIA,
  131. shaka.util.Error.Code.JS_INTEGER_OVERFLOW);
  132. }
  133. let value = 0;
  134. for (let i = 0; i < vint.length; i++) {
  135. const item = vint[i];
  136. if (i == 0) {
  137. // Mask out the first few bits of |vint|'s first byte to get the most
  138. // significant bits of |vint|'s value. If |vint| is 8 bytes wide then
  139. // |value| will be set to 0.
  140. const mask = 0x1 << (8 - vint.length);
  141. value = item & (mask - 1);
  142. } else {
  143. // Note that we cannot use << since |value| may exceed 32 bits.
  144. value = (256 * value) + item;
  145. }
  146. }
  147. return value;
  148. }
  149. /**
  150. * Checks if the given variable sized integer represents a dynamic size value.
  151. * @param {!Uint8Array} vint The variable sized integer.
  152. * @return {boolean} true if |vint| represents a dynamic size value,
  153. * false otherwise.
  154. * @private
  155. */
  156. static isDynamicSizeValue_(vint) {
  157. const EbmlParser = shaka.dash.EbmlParser;
  158. const BufferUtils = shaka.util.BufferUtils;
  159. for (const dynamicSizeConst of EbmlParser.DYNAMIC_SIZES) {
  160. if (BufferUtils.equal(vint, new Uint8Array(dynamicSizeConst))) {
  161. return true;
  162. }
  163. }
  164. return false;
  165. }
  166. };
  167. /**
  168. * A list of EBML dynamic size constants.
  169. * @const {!Array<!Array<number>>}
  170. */
  171. shaka.dash.EbmlParser.DYNAMIC_SIZES = [
  172. [0xff],
  173. [0x7f, 0xff],
  174. [0x3f, 0xff, 0xff],
  175. [0x1f, 0xff, 0xff, 0xff],
  176. [0x0f, 0xff, 0xff, 0xff, 0xff],
  177. [0x07, 0xff, 0xff, 0xff, 0xff, 0xff],
  178. [0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
  179. [0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
  180. ];
  181. shaka.util.EbmlElement = class {
  182. /**
  183. * @param {number} id The ID.
  184. * @param {!DataView} dataView The DataView.
  185. */
  186. constructor(id, dataView) {
  187. /** @type {number} */
  188. this.id = id;
  189. /** @private {!DataView} */
  190. this.dataView_ = dataView;
  191. }
  192. /**
  193. * Gets the element's offset from the beginning of the buffer.
  194. * @return {number}
  195. */
  196. getOffset() {
  197. return this.dataView_.byteOffset;
  198. }
  199. /**
  200. * Interpret the element's data as a list of sub-elements.
  201. * @return {!shaka.dash.EbmlParser} A parser over the sub-elements.
  202. */
  203. createParser() {
  204. return new shaka.dash.EbmlParser(this.dataView_);
  205. }
  206. /**
  207. * Interpret the element's data as an unsigned integer.
  208. * @return {number}
  209. */
  210. getUint() {
  211. if (this.dataView_.byteLength > 8) {
  212. throw new shaka.util.Error(
  213. shaka.util.Error.Severity.CRITICAL,
  214. shaka.util.Error.Category.MEDIA,
  215. shaka.util.Error.Code.EBML_OVERFLOW);
  216. }
  217. // Ensure we have at most 53 meaningful bits.
  218. if ((this.dataView_.byteLength == 8) &&
  219. (this.dataView_.getUint8(0) & 0xe0)) {
  220. throw new shaka.util.Error(
  221. shaka.util.Error.Severity.CRITICAL,
  222. shaka.util.Error.Category.MEDIA,
  223. shaka.util.Error.Code.JS_INTEGER_OVERFLOW);
  224. }
  225. let value = 0;
  226. for (let i = 0; i < this.dataView_.byteLength; i++) {
  227. const chunk = this.dataView_.getUint8(i);
  228. value = (256 * value) + chunk;
  229. }
  230. return value;
  231. }
  232. /**
  233. * Interpret the element's data as a floating point number
  234. * (32 bits or 64 bits). 80-bit floating point numbers are not supported.
  235. * @return {number}
  236. */
  237. getFloat() {
  238. if (this.dataView_.byteLength == 4) {
  239. return this.dataView_.getFloat32(0);
  240. } else if (this.dataView_.byteLength == 8) {
  241. return this.dataView_.getFloat64(0);
  242. } else {
  243. throw new shaka.util.Error(
  244. shaka.util.Error.Severity.CRITICAL,
  245. shaka.util.Error.Category.MEDIA,
  246. shaka.util.Error.Code.EBML_BAD_FLOATING_POINT_SIZE);
  247. }
  248. }
  249. };