/* * SWFDecoder.java * Transform * * Copyright (c) 2001-2010 Flagstone Software Ltd. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Flagstone Software Ltd. nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package com.flagstone.transform.coder; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.Stack; import com.flagstone.transform.CharacterEncoding; /** * SWFDecoder wraps an InputStream with a buffer to reduce the amount of * memory required to decode a movie and to improve efficiency by reading * data from a file or external source in blocks. */ @SuppressWarnings("PMD.TooManyMethods") public final class SWFDecoder { /** The default size, in bytes, for the internal buffer. */ public static final int BUFFER_SIZE = 4096; /** The default size, in bytes, for the reading strings. */ private static final int STR_BUFFER_SIZE = 1024; /** Bit mask applied to bytes when converting to unsigned integers. */ private static final int BYTE_MASK = 255; /** Number of bits to shift when aligning a value to the second byte. */ private static final int TO_BYTE1 = 8; /** Number of bits to shift when aligning a value to the third byte. */ private static final int TO_BYTE2 = 16; /** Number of bits to shift when aligning a value to the fourth byte. */ private static final int TO_BYTE3 = 24; /** Number of bits in an int. */ private static final int BITS_PER_INT = 32; /** Number of bits in a byte. */ private static final int BITS_PER_BYTE = 8; /** Right shift to convert number of bits to number of bytes. */ private static final int BITS_TO_BYTES = 3; /** Left shift to convert number of bytes to number of bits. */ private static final int BYTES_TO_BITS = 3; /** The underlying input stream. */ private final transient InputStream stream; /** The buffer for data read from the stream. */ private final transient byte[] buffer; /** A buffer used for reading null terminated strings. */ private transient byte[] stringBuffer; /** The character encoding used for strings. */ private transient String encoding; /** Stack for storing file locations. */ private final transient Stack<Integer>locations; /** The position of the buffer relative to the start of the stream. */ private transient int pos; /** The position from the start of the buffer. */ private transient int index; /** The offset in bits in the current buffer location. */ private transient int offset; /** The number of bytes available in the current buffer. */ private transient int size; /** The starting location from the last check-point. */ private transient int location; /** The expected number number of bytes to be decoded. */ private transient int expected; /** The difference from the expected number. */ private transient int delta; /** * Create a new SWFDecoder for the underlying InputStream with the * specified buffer size. * * @param streamIn the stream from which data will be read. * @param length the size in bytes of the buffer. */ public SWFDecoder(final InputStream streamIn, final int length) { stream = streamIn; buffer = new byte[length]; stringBuffer = new byte[STR_BUFFER_SIZE]; encoding = CharacterEncoding.UTF8.getEncoding(); locations = new Stack<Integer>(); } /** * Create a new SWFDecoder for the underlying InputStream using the * default buffer size. * * @param streamIn the stream from which data will be read. */ public SWFDecoder(final InputStream streamIn) { stream = streamIn; buffer = new byte[BUFFER_SIZE]; stringBuffer = new byte[BUFFER_SIZE]; encoding = CharacterEncoding.UTF8.getEncoding(); locations = new Stack<Integer>(); } /** * Fill the internal buffer. Any unread bytes are copied to the start of * the buffer and the remaining space is filled with data from the * underlying stream. * * @throws IOException if an error occurs reading from the underlying * input stream. */ public void fill() throws IOException { final int diff = size - index; pos += index; if (index < size) { for (int i = 0; i < diff; i++) { buffer[i] = buffer[index++]; } } int bytesRead = 0; int bytesToRead = buffer.length - diff; index = diff; size = diff; do { bytesRead = stream.read(buffer, index, bytesToRead); if (bytesRead == -1) { bytesToRead = 0; } else { index += bytesRead; size += bytesRead; bytesToRead -= bytesRead; } } while (bytesToRead > 0); index = 0; } /** * Remember the current position. * @return the current position. */ public int mark() { return locations.push(pos + index); } /** * Discard the last saved position. */ public void unmark() { locations.pop(); } /** * Reposition the decoder to the point recorded by the last call to the * mark() method. * * @throws IOException if the internal buffer was filled after mark() was * called. */ public void reset() throws IOException { int last; if (locations.isEmpty()) { last = 0; } else { last = locations.peek(); } if (last - pos < 0) { throw new IOException(); } index = last - pos; } /** * Compare the number of bytes read since the last saved position and * record any difference. * * @param count the expected number of bytes read. */ public void check(final int count) { expected = count; location = locations.peek(); delta = count - ((pos + index) - location); } /** * Get the location recorded for the last call to check(). * @return the position in the buffer of the call to mark() used by * check(). */ public int getLocation() { return location; } /** * Get the expected number of bytes from the last call to check(). * @return the difference from the expected number of bytes decoded. */ public int getExpected() { return expected; } /** * Get the difference from the expected number of bytes from the last call * to check(). * @return the difference from the expected number of bytes decoded. */ public int getDelta() { return delta; } /** * Get the number of bytes read from the last saved position. * * @return the number of bytes read since the mark() method was last called. */ public int bytesRead() { return (pos + index) - locations.peek(); } /** * Changes the location to the next byte boundary. */ public void alignToByte() { if (offset > 0) { index += 1; offset = 0; } } /** * Skips over and discards n bytes of data. * * @param count the number of bytes to skip. * * @throws IOException if an error occurs reading from the underlying * input stream. */ public void skip(final int count) throws IOException { if (size - index == 0) { fill(); } if (count < size - index) { index += count; } else { int toSkip = count; int diff; while (toSkip > 0) { diff = size - index; if (toSkip <= diff) { index += toSkip; toSkip = 0; } else { index += diff; toSkip -= diff; fill(); if (size - index == 0) { throw new ArrayIndexOutOfBoundsException(); } } } } } /** * Read a bit field. * * @param numberOfBits * the number of bits to read. * * @param signed * indicates whether the integer value read is signed. * * @return the value read. * * @throws IOException if an error occurs reading from the underlying * input stream. */ public int readBits(final int numberOfBits, final boolean signed) throws IOException { int pointer = (index << BYTES_TO_BITS) + offset; if (((size << BYTES_TO_BITS) - pointer) < numberOfBits) { fill(); pointer = (index << BYTES_TO_BITS) + offset; } int value = 0; if (numberOfBits > 0) { if (pointer + numberOfBits > (size << BYTES_TO_BITS)) { throw new ArrayIndexOutOfBoundsException(); } for (int i = BITS_PER_INT; (i > 0) && (index < buffer.length); i -= BITS_PER_BYTE) { value |= (buffer[index++] & BYTE_MASK) << (i - BITS_PER_BYTE); } value <<= offset; if (signed) { value >>= BITS_PER_INT - numberOfBits; } else { value >>>= BITS_PER_INT - numberOfBits; } pointer += numberOfBits; index = pointer >>> BITS_TO_BYTES; offset = pointer & Coder.LOWEST3; } return value; } /** * Read-ahead a bit field. * * @param numberOfBits * the number of bits to read. * * @param signed * indicates whether the integer value read is signed. * * @return the value read. * * @throws IOException if an error occurs reading from the underlying * input stream. */ public int scanBits(final int numberOfBits, final boolean signed) throws IOException { int pointer = (index << BYTES_TO_BITS) + offset; if (((size << BYTES_TO_BITS) - pointer) < numberOfBits) { fill(); pointer = (index << BYTES_TO_BITS) + offset; } int value = 0; if (numberOfBits > 0) { if (pointer + numberOfBits > (size << BYTES_TO_BITS)) { throw new ArrayIndexOutOfBoundsException(); } for (int i = BITS_PER_INT; (i > 0) && (index < buffer.length); i -= BITS_PER_BYTE) { value |= (buffer[index++] & BYTE_MASK) << (i - BITS_PER_BYTE); } value <<= offset; if (signed) { value >>= BITS_PER_INT - numberOfBits; } else { value >>>= BITS_PER_INT - numberOfBits; } index = pointer >>> BITS_TO_BYTES; offset = pointer & Coder.LOWEST3; } return value; } /** * Read an unsigned byte but do not advance the internal pointer. * * @return an 8-bit unsigned value. * * @throws IOException if an error occurs reading from the underlying * input stream. */ public int scanByte() throws IOException { if (size - index < 1) { fill(); } if (index + 1 > size) { throw new ArrayIndexOutOfBoundsException(); } return buffer[index] & BYTE_MASK; } /** * Read an unsigned byte. * * @return an 8-bit unsigned value. * * @throws IOException if an error occurs reading from the underlying * input stream. */ public int readByte() throws IOException { if (size - index < 1) { fill(); } if (index + 1 > size) { throw new ArrayIndexOutOfBoundsException(); } return buffer[index++] & BYTE_MASK; } /** * Reads an array of bytes. * * @param bytes * the array that will contain the bytes read. * * @return the array of bytes. * * @throws IOException if an error occurs reading from the underlying * input stream. */ public byte[] readBytes(final byte[] bytes) throws IOException { final int wanted = bytes.length; int dest = 0; int read = 0; int available; int remaining; while (read < wanted) { available = size - index; remaining = wanted - read; if (available > remaining) { available = remaining; } System.arraycopy(buffer, index, bytes, dest, available); read += available; index += available; dest += available; if (index == size) { fill(); } } return bytes; } /** * Sets the character encoding scheme used when encoding or decoding * strings. * * @param enc * the CharacterEncoding that identifies how strings are encoded. */ public void setEncoding(final CharacterEncoding enc) { encoding = enc.getEncoding(); } /** * Read a string using the default character set defined in the decoder. * * @param length * the number of bytes to read. * * @return the decoded string. * * @throws IOException if an error occurs reading from the underlying * input stream. */ public String readString(final int length) throws IOException { final byte[] bytes = new byte[length]; readBytes(bytes); int len; if (bytes[length - 1] == 0) { len = length - 1; } else { len = length; } return new String(bytes, 0, len, encoding); } /** * Read a null-terminated string using the default character set defined in * the decoder. * * @return the decoded string. * * @throws IOException if an error occurs reading from the underlying * input stream. */ public String readString() throws IOException { int start = index; int length = 0; int available; int dest = 0; boolean finished = false; int count; while (!finished) { available = size - index; if (available == 0) { fill(); available = size - index; } start = index; count = 0; for (int i = 0; i < available; i++) { if (buffer[index++] == 0) { finished = true; break; } else { length++; count++; } } if (stringBuffer.length < length) { stringBuffer = Arrays.copyOf(stringBuffer, length << 2); } System.arraycopy(buffer, start, stringBuffer, dest, count); dest += length; } return new String(stringBuffer, 0, length, encoding); } /** * Read an unsigned 16-bit integer. * * @return the value read. * * @throws IOException if an error occurs reading from the underlying * input stream. */ public int scanUnsignedShort() throws IOException { if (size - index < 2) { fill(); } if (index + 2 > size) { throw new ArrayIndexOutOfBoundsException(); } int value = buffer[index] & BYTE_MASK; value |= (buffer[index + 1] & BYTE_MASK) << TO_BYTE1; return value; } /** * Read an unsigned 16-bit integer. * * @return the value read. * * @throws IOException if an error occurs reading from the underlying * input stream. */ public int readUnsignedShort() throws IOException { if (size - index < 2) { fill(); } if (index + 2 > size) { throw new ArrayIndexOutOfBoundsException(); } int value = buffer[index++] & BYTE_MASK; value |= (buffer[index++] & BYTE_MASK) << TO_BYTE1; return value; } /** * Read an unsigned 16-bit integer. * * @return the value read. * * @throws IOException if an error occurs reading from the underlying * input stream. */ public int readSignedShort() throws IOException { if (size - index < 2) { fill(); } if (index + 2 > size) { throw new ArrayIndexOutOfBoundsException(); } int value = buffer[index++] & BYTE_MASK; value |= buffer[index++] << TO_BYTE1; return value; } /** * Read an unsigned 32-bit integer. * * @return the value read. * * @throws IOException if an error occurs reading from the underlying * input stream. */ public int readInt() throws IOException { if (size - index < 4) { fill(); } if (index + 4 > size) { throw new ArrayIndexOutOfBoundsException(); } int value = buffer[index++] & BYTE_MASK; value |= (buffer[index++] & BYTE_MASK) << TO_BYTE1; value |= (buffer[index++] & BYTE_MASK) << TO_BYTE2; value |= (buffer[index++] & BYTE_MASK) << TO_BYTE3; return value; } /** * Read a 32-bit unsigned integer, encoded using a variable number of bytes. * * @return the value read. * * @throws IOException if an error occurs reading from the underlying * input stream. */ public int readVarInt() throws IOException { if (size - index < 5) { fill(); } int value = buffer[index++] & BYTE_MASK; final int mask = -1; int test = Coder.BIT7; int step = Coder.VAR_INT_SHIFT; while ((value & test) != 0) { value = ((buffer[index++] & BYTE_MASK) << step) + (value & mask >>> (32 - step)); test <<= Coder.VAR_INT_SHIFT; step += Coder.VAR_INT_SHIFT; } return value; } /** * Number of bits to shift to obtain the sign in a half-precision * float-point value. */ private static final int HALF_SIGN_SHIFT = 15; /** * Number of bits to shift to obtain the exponent in a half-precision * floating-point value. */ private static final int HALF_EXP_SHIFT = 10; /** * The offset to apply to the exponent in a half-precision * floating-point value. */ private static final int HALF_EXP_OFFSET = 15; /** * The maximum value of the exponent in a half-precision * floating-point value. */ private static final int HALF_EXP_MAX = 31; /** * Number of bits to shift to obtain the sign in a single-precision * float-point value. */ private static final int SIGN_SHIFT = 31; /** * Number of bits to shift to obtain the exponent in a singlr-precision * floating-point value. */ private static final int EXP_SHIFT = 23; /** * The maximum value of the exponent in a single-precision * floating-point value. */ private static final int EXP_MAX = 127; /** * Number of bits to shift to obtain the mantissa in a single-precision * float-point value. */ private static final int MANT_SHIFT = 13; /** * The bit pattern used to represent Infinity in a single-precision * floating-point value. */ private static final int INFINITY = 0x7f800000; /** * Read a single-precision floating point number. * * @return the value. * * @throws IOException if an error occurs reading from the underlying * input stream. */ public float readHalf() throws IOException { final int bits = readUnsignedShort(); final int sign = (bits >> HALF_SIGN_SHIFT) & Coder.BIT0; int exp = (bits >> HALF_EXP_SHIFT) & Coder.LOWEST5; int mantissa = bits & Coder.LOWEST10; float value; if (exp == 0) { if (mantissa == 0) { // Plus or minus zero value = Float.intBitsToFloat(sign << SIGN_SHIFT); } else { // Denormalized number -- renormalize it while ((mantissa & Coder.BIT10) == 0) { mantissa <<= 1; exp -= 1; } exp += 1; exp = exp + (EXP_MAX - HALF_EXP_OFFSET); mantissa &= ~Coder.BIT10; mantissa = mantissa << MANT_SHIFT; value = Float.intBitsToFloat((sign << SIGN_SHIFT) | (exp << EXP_SHIFT) | mantissa); } } else if (exp == HALF_EXP_MAX) { if (mantissa == 0) { // Inf value = Float.intBitsToFloat((sign << SIGN_SHIFT) | INFINITY); } else { // NaN value = Float.intBitsToFloat((sign << SIGN_SHIFT) | INFINITY | (mantissa << MANT_SHIFT)); } } else { exp = exp + (EXP_MAX - HALF_EXP_OFFSET); mantissa = mantissa << MANT_SHIFT; value = Float.intBitsToFloat((sign << SIGN_SHIFT) | (exp << EXP_SHIFT) | mantissa); } return value; } }