1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.mkv; 17 18 import androidx.annotation.IntDef; 19 import org.mozilla.thirdparty.com.google.android.exoplayer2.C; 20 import org.mozilla.thirdparty.com.google.android.exoplayer2.ParserException; 21 import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.ExtractorInput; 22 import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Assertions; 23 import java.io.EOFException; 24 import java.io.IOException; 25 import java.lang.annotation.Documented; 26 import java.lang.annotation.Retention; 27 import java.lang.annotation.RetentionPolicy; 28 import java.util.ArrayDeque; 29 30 /** 31 * Default implementation of {@link EbmlReader}. 32 */ 33 /* package */ final class DefaultEbmlReader implements EbmlReader { 34 35 @Documented 36 @Retention(RetentionPolicy.SOURCE) 37 @IntDef({ELEMENT_STATE_READ_ID, ELEMENT_STATE_READ_CONTENT_SIZE, ELEMENT_STATE_READ_CONTENT}) 38 private @interface ElementState {} 39 40 private static final int ELEMENT_STATE_READ_ID = 0; 41 private static final int ELEMENT_STATE_READ_CONTENT_SIZE = 1; 42 private static final int ELEMENT_STATE_READ_CONTENT = 2; 43 44 private static final int MAX_ID_BYTES = 4; 45 private static final int MAX_LENGTH_BYTES = 8; 46 47 private static final int MAX_INTEGER_ELEMENT_SIZE_BYTES = 8; 48 private static final int VALID_FLOAT32_ELEMENT_SIZE_BYTES = 4; 49 private static final int VALID_FLOAT64_ELEMENT_SIZE_BYTES = 8; 50 51 private final byte[] scratch; 52 private final ArrayDeque<MasterElement> masterElementsStack; 53 private final VarintReader varintReader; 54 55 private EbmlProcessor processor; 56 private @ElementState int elementState; 57 private int elementId; 58 private long elementContentSize; 59 DefaultEbmlReader()60 public DefaultEbmlReader() { 61 scratch = new byte[8]; 62 masterElementsStack = new ArrayDeque<>(); 63 varintReader = new VarintReader(); 64 } 65 66 @Override init(EbmlProcessor processor)67 public void init(EbmlProcessor processor) { 68 this.processor = processor; 69 } 70 71 @Override reset()72 public void reset() { 73 elementState = ELEMENT_STATE_READ_ID; 74 masterElementsStack.clear(); 75 varintReader.reset(); 76 } 77 78 @Override read(ExtractorInput input)79 public boolean read(ExtractorInput input) throws IOException, InterruptedException { 80 Assertions.checkNotNull(processor); 81 while (true) { 82 if (!masterElementsStack.isEmpty() 83 && input.getPosition() >= masterElementsStack.peek().elementEndPosition) { 84 processor.endMasterElement(masterElementsStack.pop().elementId); 85 return true; 86 } 87 88 if (elementState == ELEMENT_STATE_READ_ID) { 89 long result = varintReader.readUnsignedVarint(input, true, false, MAX_ID_BYTES); 90 if (result == C.RESULT_MAX_LENGTH_EXCEEDED) { 91 result = maybeResyncToNextLevel1Element(input); 92 } 93 if (result == C.RESULT_END_OF_INPUT) { 94 return false; 95 } 96 // Element IDs are at most 4 bytes, so we can cast to integers. 97 elementId = (int) result; 98 elementState = ELEMENT_STATE_READ_CONTENT_SIZE; 99 } 100 101 if (elementState == ELEMENT_STATE_READ_CONTENT_SIZE) { 102 elementContentSize = varintReader.readUnsignedVarint(input, false, true, MAX_LENGTH_BYTES); 103 elementState = ELEMENT_STATE_READ_CONTENT; 104 } 105 106 @EbmlProcessor.ElementType int type = processor.getElementType(elementId); 107 switch (type) { 108 case EbmlProcessor.ELEMENT_TYPE_MASTER: 109 long elementContentPosition = input.getPosition(); 110 long elementEndPosition = elementContentPosition + elementContentSize; 111 masterElementsStack.push(new MasterElement(elementId, elementEndPosition)); 112 processor.startMasterElement(elementId, elementContentPosition, elementContentSize); 113 elementState = ELEMENT_STATE_READ_ID; 114 return true; 115 case EbmlProcessor.ELEMENT_TYPE_UNSIGNED_INT: 116 if (elementContentSize > MAX_INTEGER_ELEMENT_SIZE_BYTES) { 117 throw new ParserException("Invalid integer size: " + elementContentSize); 118 } 119 processor.integerElement(elementId, readInteger(input, (int) elementContentSize)); 120 elementState = ELEMENT_STATE_READ_ID; 121 return true; 122 case EbmlProcessor.ELEMENT_TYPE_FLOAT: 123 if (elementContentSize != VALID_FLOAT32_ELEMENT_SIZE_BYTES 124 && elementContentSize != VALID_FLOAT64_ELEMENT_SIZE_BYTES) { 125 throw new ParserException("Invalid float size: " + elementContentSize); 126 } 127 processor.floatElement(elementId, readFloat(input, (int) elementContentSize)); 128 elementState = ELEMENT_STATE_READ_ID; 129 return true; 130 case EbmlProcessor.ELEMENT_TYPE_STRING: 131 if (elementContentSize > Integer.MAX_VALUE) { 132 throw new ParserException("String element size: " + elementContentSize); 133 } 134 processor.stringElement(elementId, readString(input, (int) elementContentSize)); 135 elementState = ELEMENT_STATE_READ_ID; 136 return true; 137 case EbmlProcessor.ELEMENT_TYPE_BINARY: 138 processor.binaryElement(elementId, (int) elementContentSize, input); 139 elementState = ELEMENT_STATE_READ_ID; 140 return true; 141 case EbmlProcessor.ELEMENT_TYPE_UNKNOWN: 142 input.skipFully((int) elementContentSize); 143 elementState = ELEMENT_STATE_READ_ID; 144 break; 145 default: 146 throw new ParserException("Invalid element type " + type); 147 } 148 } 149 } 150 151 /** 152 * Does a byte by byte search to try and find the next level 1 element. This method is called if 153 * some invalid data is encountered in the parser. 154 * 155 * @param input The {@link ExtractorInput} from which data has to be read. 156 * @return id of the next level 1 element that has been found. 157 * @throws EOFException If the end of input was encountered when searching for the next level 1 158 * element. 159 * @throws IOException If an error occurs reading from the input. 160 * @throws InterruptedException If the thread is interrupted. 161 */ maybeResyncToNextLevel1Element(ExtractorInput input)162 private long maybeResyncToNextLevel1Element(ExtractorInput input) throws IOException, 163 InterruptedException { 164 input.resetPeekPosition(); 165 while (true) { 166 input.peekFully(scratch, 0, MAX_ID_BYTES); 167 int varintLength = VarintReader.parseUnsignedVarintLength(scratch[0]); 168 if (varintLength != C.LENGTH_UNSET && varintLength <= MAX_ID_BYTES) { 169 int potentialId = (int) VarintReader.assembleVarint(scratch, varintLength, false); 170 if (processor.isLevel1Element(potentialId)) { 171 input.skipFully(varintLength); 172 return potentialId; 173 } 174 } 175 input.skipFully(1); 176 } 177 } 178 179 /** 180 * Reads and returns an integer of length {@code byteLength} from the {@link ExtractorInput}. 181 * 182 * @param input The {@link ExtractorInput} from which to read. 183 * @param byteLength The length of the integer being read. 184 * @return The read integer value. 185 * @throws IOException If an error occurs reading from the input. 186 * @throws InterruptedException If the thread is interrupted. 187 */ readInteger(ExtractorInput input, int byteLength)188 private long readInteger(ExtractorInput input, int byteLength) 189 throws IOException, InterruptedException { 190 input.readFully(scratch, 0, byteLength); 191 long value = 0; 192 for (int i = 0; i < byteLength; i++) { 193 value = (value << 8) | (scratch[i] & 0xFF); 194 } 195 return value; 196 } 197 198 /** 199 * Reads and returns a float of length {@code byteLength} from the {@link ExtractorInput}. 200 * 201 * @param input The {@link ExtractorInput} from which to read. 202 * @param byteLength The length of the float being read. 203 * @return The read float value. 204 * @throws IOException If an error occurs reading from the input. 205 * @throws InterruptedException If the thread is interrupted. 206 */ readFloat(ExtractorInput input, int byteLength)207 private double readFloat(ExtractorInput input, int byteLength) 208 throws IOException, InterruptedException { 209 long integerValue = readInteger(input, byteLength); 210 double floatValue; 211 if (byteLength == VALID_FLOAT32_ELEMENT_SIZE_BYTES) { 212 floatValue = Float.intBitsToFloat((int) integerValue); 213 } else { 214 floatValue = Double.longBitsToDouble(integerValue); 215 } 216 return floatValue; 217 } 218 219 /** 220 * Reads a string of length {@code byteLength} from the {@link ExtractorInput}. Zero padding is 221 * removed, so the returned string may be shorter than {@code byteLength}. 222 * 223 * @param input The {@link ExtractorInput} from which to read. 224 * @param byteLength The length of the string being read, including zero padding. 225 * @return The read string value. 226 * @throws IOException If an error occurs reading from the input. 227 * @throws InterruptedException If the thread is interrupted. 228 */ readString(ExtractorInput input, int byteLength)229 private String readString(ExtractorInput input, int byteLength) 230 throws IOException, InterruptedException { 231 if (byteLength == 0) { 232 return ""; 233 } 234 byte[] stringBytes = new byte[byteLength]; 235 input.readFully(stringBytes, 0, byteLength); 236 // Remove zero padding. 237 int trimmedLength = byteLength; 238 while (trimmedLength > 0 && stringBytes[trimmedLength - 1] == 0) { 239 trimmedLength--; 240 } 241 return new String(stringBytes, 0, trimmedLength); 242 } 243 244 /** 245 * Used in {@link #masterElementsStack} to track when the current master element ends, so that 246 * {@link EbmlProcessor#endMasterElement(int)} can be called. 247 */ 248 private static final class MasterElement { 249 250 private final int elementId; 251 private final long elementEndPosition; 252 MasterElement(int elementId, long elementEndPosition)253 private MasterElement(int elementId, long elementEndPosition) { 254 this.elementId = elementId; 255 this.elementEndPosition = elementEndPosition; 256 } 257 258 } 259 260 } 261