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