blob: 1e823b63d5b2c89f6f4e852708c259479e23d0cd [file] [log] [blame]
/*
* Copyright (C) 2017 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.android.server.usb.descriptors;
// Framework builds and Android Studio builds use different imports for NonNull.
// This one for Framework builds
import android.annotation.NonNull;
// this one in the AndroidStudio project
// import android.support.annotation.NonNull;
/**
* @hide
* A stream interface wrapping a byte array. Very much like a java.io.ByteArrayInputStream
* but with the capability to "back up" in situations where the parser discovers that a
* UsbDescriptor has overrun its length.
*/
public final class ByteStream {
private static final String TAG = "ByteStream";
/** The byte array being wrapped */
@NonNull
private final byte[] mBytes; // this is never null.
/**
* The index into the byte array to be read next.
* This value is altered by reading data out of the stream
* (using either the getByte() or unpack*() methods), or alternatively
* by explicitly offseting the stream position with either
* advance() or reverse().
*/
private int mIndex;
/*
* This member used with resetReadCount() & getReadCount() can be used to determine how many
* bytes a UsbDescriptor subclass ACTUALLY reads (as opposed to what its length field says).
* using this info, the parser can mark a descriptor as valid or invalid and correct the stream
* position with advance() & reverse() to keep from "getting lost" in the descriptor stream.
*/
private int mReadCount;
/**
* Create a ByteStream object wrapping the specified byte array.
*
* @param bytes The byte array containing the raw descriptor information retrieved from
* the USB device.
* @throws IllegalArgumentException
*/
public ByteStream(@NonNull byte[] bytes) {
if (bytes == null) {
throw new IllegalArgumentException();
}
mBytes = bytes;
}
/**
* Resets the running count of bytes read so that later we can see how much more has been read.
*/
public void resetReadCount() {
mReadCount = 0;
}
/**
* Retrieves the running count of bytes read from the stream.
*/
public int getReadCount() {
return mReadCount;
}
/**
* @return The value of the next byte in the stream without advancing the stream.
* Does not affect the running count as the byte hasn't been "consumed".
* @throws IndexOutOfBoundsException
*/
public byte peekByte() {
if (available() > 0) {
return mBytes[mIndex + 1];
} else {
throw new IndexOutOfBoundsException();
}
}
/**
* @return the next byte from the stream and advances the stream and the read count. Note
* that this is a signed byte (as is the case of byte in Java). The user may need to understand
* from context if it should be interpreted as an unsigned value.
* @throws IndexOutOfBoundsException
*/
public byte getByte() {
if (available() > 0) {
mReadCount++;
return mBytes[mIndex++];
} else {
throw new IndexOutOfBoundsException();
}
}
/**
* @return the next byte from the stream and advances the stream and the read count. Note
* that this is an unsigned byte encoded in a Java int.
* @throws IndexOutOfBoundsException
*/
public int getUnsignedByte() {
if (available() > 0) {
mReadCount++;
return mBytes[mIndex++] & 0x000000FF;
} else {
throw new IndexOutOfBoundsException();
}
}
/**
* Reads 2 bytes in *little endian format* from the stream and composes a 16-bit integer.
* As we are storing the 2-byte value in a 4-byte integer, the upper 2 bytes are always
* 0, essentially making the returned value *unsigned*.
* @return The 16-bit integer (packed into the lower 2 bytes of an int) encoded by the
* next 2 bytes in the stream.
* @throws IndexOutOfBoundsException
*/
public int unpackUsbShort() {
if (available() >= 2) {
int b0 = getUnsignedByte();
int b1 = getUnsignedByte();
return (b1 << 8) | b0;
} else {
throw new IndexOutOfBoundsException();
}
}
/**
* Reads 3 bytes in *little endian format* from the stream and composes a 24-bit integer.
* As we are storing the 3-byte value in a 4-byte integer, the upper byte is always
* 0, essentially making the returned value *unsigned*.
* @return The 24-bit integer (packed into the lower 3 bytes of an int) encoded by the
* next 3 bytes in the stream.
* @throws IndexOutOfBoundsException
*/
public int unpackUsbTriple() {
if (available() >= 3) {
int b0 = getUnsignedByte();
int b1 = getUnsignedByte();
int b2 = getUnsignedByte();
return (b2 << 16) | (b1 << 8) | b0;
} else {
throw new IndexOutOfBoundsException();
}
}
/**
* Reads 4 bytes in *little endian format* from the stream and composes a 32-bit integer.
* @return The 32-bit integer encoded by the next 4 bytes in the stream.
* @throws IndexOutOfBoundsException
*/
public int unpackUsbInt() {
if (available() >= 4) {
int b0 = getUnsignedByte();
int b1 = getUnsignedByte();
int b2 = getUnsignedByte();
int b3 = getUnsignedByte();
return (b3 << 24) | (b2 << 16) | (b1 << 8) | b0;
} else {
throw new IndexOutOfBoundsException();
}
}
/**
* Advances the logical position in the stream. Affects the running count also.
* @param numBytes The number of bytes to advance.
* @throws IndexOutOfBoundsException
* @throws IllegalArgumentException
*/
public void advance(int numBytes) {
if (numBytes < 0) {
// Positive offsets only
throw new IllegalArgumentException();
}
// do arithmetic and comparison in long to ovoid potention integer overflow
long longNewIndex = (long) mIndex + (long) numBytes;
if (longNewIndex < (long) mBytes.length) {
mReadCount += numBytes;
mIndex += numBytes;
} else {
throw new IndexOutOfBoundsException();
}
}
/**
* Reverse the logical position in the stream. Affects the running count also.
* @param numBytes The (positive) number of bytes to reverse.
* @throws IndexOutOfBoundsException
* @throws IllegalArgumentException
*/
public void reverse(int numBytes) {
if (numBytes < 0) {
// Positive (reverse) offsets only
throw new IllegalArgumentException();
}
if (mIndex >= numBytes) {
mReadCount -= numBytes;
mIndex -= numBytes;
} else {
throw new IndexOutOfBoundsException();
}
}
/**
* @return The number of bytes available to be read in the stream.
*/
public int available() {
return mBytes.length - mIndex;
}
}