Source: lib/util/ebml_parser.js

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