blob: 57ca3ed883baa341947433f9bde8a72a863ba8f8 [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.flex.swf.io;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.zip.InflaterInputStream;
import org.apache.commons.io.IOUtils;
import org.apache.flex.swf.Header;
import org.apache.flex.utils.DAByteArrayOutputStream;
/**
* Implementation of {@link InputBitStream}. This implementation allows you to
* swap the underlying {@code InputStream} source with another one.
* <p>
* {@code InputBitStream} doesn't buffer the source InputStream internally. It
* has one byte buffer for reading SWF bit-values. The buffer is filled 8 bits
* at a time. All the bit value methods read from this buffer.
* <p>
* If a SWF file is compressed, InputBitStream uses an
* {@link InflaterInputStream} to decompress the source input.
*/
public class InputBitStream extends InputStream implements IInputBitStream
{
// source
private InputStream in;
// bit value cache
private int bitPos = 0;
private int bitBuf = 0;
private long offset = 0;
private long readBoundary = 0;
/**
* Create an {@code InputBitStream}.
*
* @param in source {@code InputStream}
*/
public InputBitStream(InputStream in)
{
this.in = in;
}
public InputBitStream(byte[] bytes)
{
this.in = new ByteArrayInputStream(bytes);
}
/**
* Discard the data left in the bit value cache. Always call this method
* after reading bit values and before reading other byte-aligned data.
*/
@Override
public void byteAlign()
{
bitPos = 0;
}
@Override
public int read() throws IOException
{
return readByte();
}
@Override
public byte[] read(int length)
{
final byte[] data = new byte[length];
try
{
read(data);
}
catch (IOException e)
{
throw new RuntimeException(e);
}
return data;
}
@Override
public boolean readBit()
{
return readBits(1) != 0;
}
/**
* Read multiple bits as a padded int.
*
* @param length number of bits to read
* @return The value that was read.
*/
protected int readBits(int length)
{
if (length == 0)
{
return 0;
}
int bitsLeft = length;
int result = 0;
if (bitPos == 0) // no value in the buffer - read a byte
{
bitBuf = readUI8();
bitPos = 8;
}
while (true)
{
int shift = bitsLeft - bitPos;
if (shift > 0)
{
// Consume the entire buffer
result |= bitBuf << shift;
bitsLeft -= bitPos;
// Get the next byte from the input stream
bitBuf = readUI8();
bitPos = 8;
}
else
{
// Consume a portion of the buffer
result |= bitBuf >> -shift;
bitPos -= bitsLeft;
bitBuf &= 0xff >> (8 - bitPos); // mask off the consumed bits
return result;
}
}
}
// The following are SWF primitive type decoder methods.
/**
* This helps to mute the {@code IOException}. If there's no data in the
* source input stream, calling this method will raise an runtime exception.
* <p>
* All the methods implementing {@code IInputBitStream} should call this
* method when consuming the next byte from the input stream.
*
* @return next byte in the input stream
*/
protected int readByte()
{
byteAlign();
try
{
if (offset >= readBoundary)
{
throw new RuntimeException(String.format("About to read over or reading over the boundary: %d -> %d.", offset, readBoundary));
}
final int n = in.read();
offset++;
if (-1 == n)
{
throw new RuntimeException("No more data to read.");
}
else
{
return n;
}
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
@Override
public double readDOUBLE()
{
return Double.longBitsToDouble(readSI64());
}
@Override
public long readEncodedU32()
{
long decoded = 0;
for (int i = 0; i < 5; i++)
{
final int nextByte = readByte();
decoded = (decoded << 7) | (nextByte & 0x7f);
if ((nextByte & 0x80) == 0)
{
break;
}
}
return decoded & 0x000000ff << 24 |
decoded & 0x0000ff00 << 8 |
decoded & 0x00ff0000 >>> 8 |
decoded & 0xff000000 >>> 24;
}
@Override
public float readFB(int length)
{
// Convert bits to x.16 FIXED point number
return readSB(length) / (float)0x10000;
}
@Override
public float readFIXED()
{
// Convert 32-bit int to 16.16 FIXED point number
return readSI32() / (float)0x10000;
}
@Override
public float readFIXED8()
{
// Convert 16-bit int to 8.8 FIXED point number
return (short)readSI16() / (float)0x100;
}
@Override
public float readFLOAT()
{
return Float.intBitsToFloat(readSI32());
}
@Override
public int readSB(int length)
{
int bits = readBits(length);
return bits << 32 - length >> 32 - length;
}
@Override
public short readSI16()
{
return (short)(readByte() | readByte() << 8);
}
@Override
public int readSI32()
{
return readByte() |
readByte() << 8 |
readByte() << 16 |
readByte() << 24;
}
@Override
public long readSI64()
{
return (readUI32() & 0xFFFFFFFFL) | (readUI32() << 32);
}
@Override
public byte readSI8()
{
return (byte)readByte();
}
@Override
public String readString()
{
final DAByteArrayOutputStream buffer = new DAByteArrayOutputStream();
for (int nextByte = readUI8(); nextByte != 0; nextByte = readUI8())
{
buffer.write(nextByte);
}
try
{
return new String(buffer.getDirectByteArray(), 0, buffer.size(), "UTF-8");
}
catch (UnsupportedEncodingException e)
{
throw new RuntimeException(e);
}
finally
{
IOUtils.closeQuietly(buffer);
}
}
@Override
public int readUB(int length)
{
return readBits(length);
}
@Override
public int readUI16()
{
return 0xFFFF & readSI16();
}
@Override
public int readUI24()
{
return 0xFFFFFF & (readByte() | readByte() << 8 | readByte() << 16);
}
@Override
public long readUI32()
{
return 0xFFFFFFFFl & readSI32();
}
@Override
public short readUI8()
{
return (short)(0xFF & readSI8());
}
/**
* Set if the InputStream is a compressed SWF stream.
*/
public void setCompress(Header.Compression compression) throws IOException
{
switch (compression)
{
case NONE:
break;
case ZLIB:
this.in = new BufferedInputStream(new InflaterInputStream(in));
break;
case LZMA:
this.in = new LZMAInputStream(in);
break;
default:
assert false;
}
}
@Override
public byte[] readToBoundary()
{
assert readBoundary > 0 : "Must set boundary before readToBoundary";
// The conversion is safe because a tag length is SI32.
final int len = (int)(readBoundary - offset);
final byte[] result = this.read(len);
return result;
}
@Override
public long getOffset()
{
return offset;
}
@Override
public void setReadBoundary(long offset)
{
assert offset > 0 : "Read boundary must > 0.";
this.readBoundary = offset;
}
@Override
public long getReadBoundary()
{
return this.readBoundary;
}
}