blob: a6ee6d9cc16ad977ba77ac0ad980c667769caad2 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* 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.
*/
package com.google.android.exoplayer2.extractor.mkv;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import java.io.EOFException;
import java.io.IOException;
/** Reads EBML variable-length integers (varints) from an {@link ExtractorInput}. */
/* package */ final class VarintReader {
private static final int STATE_BEGIN_READING = 0;
private static final int STATE_READ_CONTENTS = 1;
/**
* The first byte of a variable-length integer (varint) will have one of these bit masks
* indicating the total length in bytes.
*
* <p>{@code 0x80} is a one-byte integer, {@code 0x40} is two bytes, and so on up to eight bytes.
*/
private static final long[] VARINT_LENGTH_MASKS =
new long[] {0x80L, 0x40L, 0x20L, 0x10L, 0x08L, 0x04L, 0x02L, 0x01L};
private final byte[] scratch;
private int state;
private int length;
public VarintReader() {
scratch = new byte[8];
}
/** Resets the reader to start reading a new variable-length integer. */
public void reset() {
state = STATE_BEGIN_READING;
length = 0;
}
/**
* Reads an EBML variable-length integer (varint) from an {@link ExtractorInput} such that reading
* can be resumed later if an error occurs having read only some of it.
*
* <p>If an value is successfully read, then the reader will automatically reset itself ready to
* read another value.
*
* <p>If an {@link IOException} is thrown, the read can be resumed later by calling this method
* again, passing an {@link ExtractorInput} providing data starting where the previous one left
* off.
*
* @param input The {@link ExtractorInput} from which the integer should be read.
* @param allowEndOfInput True if encountering the end of the input having read no data is
* allowed, and should result in {@link C#RESULT_END_OF_INPUT} being returned. False if it
* should be considered an error, causing an {@link EOFException} to be thrown.
* @param removeLengthMask Removes the variable-length integer length mask from the value.
* @param maximumAllowedLength Maximum allowed length of the variable integer to be read.
* @return The read value, or {@link C#RESULT_END_OF_INPUT} if {@code allowEndOfStream} is true
* and the end of the input was encountered, or {@link C#RESULT_MAX_LENGTH_EXCEEDED} if the
* length of the varint exceeded maximumAllowedLength.
* @throws IOException If an error occurs reading from the input.
*/
public long readUnsignedVarint(
ExtractorInput input,
boolean allowEndOfInput,
boolean removeLengthMask,
int maximumAllowedLength)
throws IOException {
if (state == STATE_BEGIN_READING) {
// Read the first byte to establish the length.
if (!input.readFully(scratch, 0, 1, allowEndOfInput)) {
return C.RESULT_END_OF_INPUT;
}
int firstByte = scratch[0] & 0xFF;
length = parseUnsignedVarintLength(firstByte);
if (length == C.LENGTH_UNSET) {
throw new IllegalStateException("No valid varint length mask found");
}
state = STATE_READ_CONTENTS;
}
if (length > maximumAllowedLength) {
state = STATE_BEGIN_READING;
return C.RESULT_MAX_LENGTH_EXCEEDED;
}
if (length != 1) {
// Read the remaining bytes.
input.readFully(scratch, 1, length - 1);
}
state = STATE_BEGIN_READING;
return assembleVarint(scratch, length, removeLengthMask);
}
/** Returns the number of bytes occupied by the most recently parsed varint. */
public int getLastLength() {
return length;
}
/**
* Parses and the length of the varint given the first byte.
*
* @param firstByte First byte of the varint.
* @return Length of the varint beginning with the given byte if it was valid, {@link
* C#LENGTH_UNSET} otherwise.
*/
public static int parseUnsignedVarintLength(int firstByte) {
int varIntLength = C.LENGTH_UNSET;
for (int i = 0; i < VARINT_LENGTH_MASKS.length; i++) {
if ((VARINT_LENGTH_MASKS[i] & firstByte) != 0) {
varIntLength = i + 1;
break;
}
}
return varIntLength;
}
/**
* Assemble a varint from the given byte array.
*
* @param varintBytes Bytes that make up the varint.
* @param varintLength Length of the varint to assemble.
* @param removeLengthMask Removes the variable-length integer length mask from the value.
* @return Parsed and assembled varint.
*/
public static long assembleVarint(
byte[] varintBytes, int varintLength, boolean removeLengthMask) {
long varint = varintBytes[0] & 0xFFL;
if (removeLengthMask) {
varint &= ~VARINT_LENGTH_MASKS[varintLength - 1];
}
for (int i = 1; i < varintLength; i++) {
varint = (varint << 8) | (varintBytes[i] & 0xFFL);
}
return varint;
}
}