/* * Copyright 2002-2015 Drew Noakes * * More information about this project is available at: * * https://drewnoakes.com/code/exif/ * https://github.com/drewnoakes/metadata-extractor */ package tim.prune.jpeg.drew; import java.io.IOException; /** * Provides methods to read specific values from a byte array, * with a consistent, checked exception structure for issues. * * @author Drew Noakes https://drewnoakes.com */ public class ByteArrayReader { private final byte[] _buffer; private boolean _isMotorolaByteOrder = true; public ByteArrayReader(byte[] buffer) { if (buffer == null) throw new NullPointerException(); _buffer = buffer; } public void setMotorolaByteOrder(boolean motorolaByteOrder) { _isMotorolaByteOrder = motorolaByteOrder; } public long getLength() { return _buffer.length; } protected byte getByte(int index) { return _buffer[index]; } protected void validateIndex(int index, int bytesRequested) throws ExifException { if (!isValidIndex(index, bytesRequested)) throw new ExifException("Invalid index " + index); } private boolean isValidIndex(int index, int bytesRequested) { return bytesRequested >= 0 && index >= 0 && ((long)index + (long)bytesRequested) <= (long)_buffer.length; } public byte[] getBytes(int index, int count) throws ExifException { validateIndex(index, count); byte[] bytes = new byte[count]; System.arraycopy(_buffer, index, bytes, 0, count); return bytes; } /** * Returns an unsigned 8-bit int calculated from one byte of data at the specified index. * * @param index position within the data buffer to read byte * @return the 8 bit int value, between 0 and 255 * @throws IOException the buffer does not contain enough bytes to service the request, or index is negative */ public short getUInt8(int index) throws ExifException { validateIndex(index, 1); return (short) (getByte(index) & 0xFF); } /** * Returns a signed 8-bit int calculated from one byte of data at the specified index. * * @param index position within the data buffer to read byte * @return the 8 bit int value, between 0x00 and 0xFF * @throws IOException the buffer does not contain enough bytes to service the request, or index is negative */ public byte getInt8(int index) throws ExifException { validateIndex(index, 1); return getByte(index); } /** * Returns an unsigned 16-bit int calculated from two bytes of data at the specified index. * * @param index position within the data buffer to read first byte * @return the 16 bit int value, between 0x0000 and 0xFFFF * @throws IOException the buffer does not contain enough bytes to service the request, or index is negative */ public int getUInt16(int index) throws ExifException { validateIndex(index, 2); if (_isMotorolaByteOrder) { // Motorola - MSB first return (getByte(index ) << 8 & 0xFF00) | (getByte(index + 1) & 0xFF); } else { // Intel ordering - LSB first return (getByte(index + 1) << 8 & 0xFF00) | (getByte(index ) & 0xFF); } } /** * Returns a signed 16-bit int calculated from two bytes of data at the specified index (MSB, LSB). * * @param index position within the data buffer to read first byte * @return the 16 bit int value, between 0x0000 and 0xFFFF * @throws IOException the buffer does not contain enough bytes to service the request, or index is negative */ public short getInt16(int index) throws ExifException { validateIndex(index, 2); if (_isMotorolaByteOrder) { // Motorola - MSB first return (short) (((short)getByte(index ) << 8 & (short)0xFF00) | ((short)getByte(index + 1) & (short)0xFF)); } else { // Intel ordering - LSB first return (short) (((short)getByte(index + 1) << 8 & (short)0xFF00) | ((short)getByte(index ) & (short)0xFF)); } } /** * Get a 32-bit unsigned integer from the buffer, returning it as a long. * * @param index position within the data buffer to read first byte * @return the unsigned 32-bit int value as a long, between 0x00000000 and 0xFFFFFFFF * @throws IOException the buffer does not contain enough bytes to service the request, or index is negative */ public long getUInt32(int index) throws ExifException { validateIndex(index, 4); if (_isMotorolaByteOrder) { // Motorola - MSB first (big endian) return (((long)getByte(index )) << 24 & 0xFF000000L) | (((long)getByte(index + 1)) << 16 & 0xFF0000L) | (((long)getByte(index + 2)) << 8 & 0xFF00L) | (((long)getByte(index + 3)) & 0xFFL); } else { // Intel ordering - LSB first (little endian) return (((long)getByte(index + 3)) << 24 & 0xFF000000L) | (((long)getByte(index + 2)) << 16 & 0xFF0000L) | (((long)getByte(index + 1)) << 8 & 0xFF00L) | (((long)getByte(index )) & 0xFFL); } } /** * Returns a signed 32-bit integer from four bytes of data at the specified index the buffer. * * @param index position within the data buffer to read first byte * @return the signed 32 bit int value, between 0x00000000 and 0xFFFFFFFF * @throws IOException the buffer does not contain enough bytes to service the request, or index is negative */ public int getInt32(int index) throws ExifException { validateIndex(index, 4); if (_isMotorolaByteOrder) { // Motorola - MSB first (big endian) return (getByte(index ) << 24 & 0xFF000000) | (getByte(index + 1) << 16 & 0xFF0000) | (getByte(index + 2) << 8 & 0xFF00) | (getByte(index + 3) & 0xFF); } else { // Intel ordering - LSB first (little endian) return (getByte(index + 3) << 24 & 0xFF000000) | (getByte(index + 2) << 16 & 0xFF0000) | (getByte(index + 1) << 8 & 0xFF00) | (getByte(index ) & 0xFF); } } /** * Creates a String from the _data buffer starting at the specified index, * and ending where byte=='\0' or where length==maxLength. * * @param index The index within the buffer at which to start reading the string. * @param maxLengthBytes The maximum number of bytes to read. If a zero-byte is not reached within this limit, * reading will stop and the string will be truncated to this length. * @return The read string. * @throws IOException The buffer does not contain enough bytes to satisfy this request. */ public String getNullTerminatedString(int index, int maxLengthBytes) throws ExifException { // NOTE currently only really suited to single-byte character strings byte[] bytes = getBytes(index, maxLengthBytes); // Count the number of non-null bytes int length = 0; while (length < bytes.length && bytes[length] != '\0') length++; return new String(bytes, 0, length); } public String getString(int index, int bytesRequested) throws ExifException { // TODO: validate index return new String(getBytes(index, bytesRequested)); } }