Replaced com.riiablo.io classes with com.riiablo.io.nio classes

This commit is contained in:
Collin Smith 2020-08-08 14:49:23 -07:00
parent c8b74fccbf
commit 7ed92b1f49
6 changed files with 329 additions and 1091 deletions

View File

@ -1,19 +1,20 @@
package com.riiablo.io;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.apache.commons.lang3.StringUtils;
import com.riiablo.util.DebugUtils;
/**
* Wraps a {@link ByteInput} to support reading sequences of bits and
* supporting {@link #align() re-aligning} the byte stream to read sequences of
* {@link ByteInput bytes}. All read functions will return results in little
* endian byte order.
*
* @see BitInput
* @see #align()
*/
// TODO: improve placeholder documentation
public class BitInput {
private static final BitInput EMPTY_BITINPUT = new BitInput(new byte[0]);
public static BitInput emptyBitInput() {
return EMPTY_BITINPUT;
}
public static BitInput wrap(byte[] bytes) {
return bytes == null ? emptyBitInput() : new BitInput(bytes);
return ByteInput.wrap(bytes).unalign();
}
private static final int MAX_ULONG_BITS = Long.SIZE - 1;
@ -31,75 +32,147 @@ public class BitInput {
}
}
private final ByteBuf buffer;
private final ByteInput byteInput;
private final long numBits;
private long bitsRead;
private int bitsCached;
private long cache;
private BitInput(byte[] b) {
buffer = Unpooled.wrappedBuffer(b);
numBits = (long) b.length * Byte.SIZE;
/**
* Constructs a BitInput instance at the start of the byteInput with
* {@code numBits} including all bits within {@code byteInput}.
*/
BitInput(ByteInput byteInput) {
this(byteInput, 0, 0L, (long) byteInput.bytesRemaining() * Byte.SIZE);
}
/**
* Clears the overflow bits of the previously read byte.
* Constructs a BitInput instance with an initial state. This is typically
* done when the BitInput is created as the child of another BitInput.
*/
public void clearCache() {
private BitInput(ByteInput byteInput, int bitsCached, long cache, long numBits) {
this.byteInput = byteInput;
this.bitsCached = bitsCached;
this.cache = cache;
this.numBits = numBits;
}
ByteInput byteInput() {
return byteInput;
}
public int bytesRead() {
return byteInput.bytesRead();
}
public int bytesRemaining() {
return byteInput.bytesRemaining();
}
public int numBytes() {
return byteInput.numBytes();
}
public int bitsCached() {
return bitsCached;
}
public long cache() {
return cache;
}
void clearCache() {
bitsCached = 0;
cache = 0L;
}
int bitsCached() {
return bitsCached;
}
long cache() {
return cache;
}
public long bitsRead() {
return bitsRead;
}
public long numBits() {
return numBits;
}
// TODO: include parent offset?
public int bytePosition() {
return buffer.readerIndex();
}
public int bytesRemaining() {
return buffer.readableBytes();
}
// TODO: include parent offset?
public long bitPosition() {
assert bitsRead == ((Byte.SIZE - bitsCached) + ((long) bytePosition() * Byte.SIZE))
: "actual(" + bitsRead + ") != expected(" + ((Byte.SIZE - bitsCached) + ((long) bytePosition() * Byte.SIZE)) + ")";
return bitsRead;
}
public long bitsRemaining() {
assert (numBits - bitsRead) == (bitsCached + ((long) bytesRemaining() * Byte.SIZE))
: "actual(" + (numBits - bitsRead) + ") != expected(" + (bitsCached + ((long) bytesRemaining() * Byte.SIZE)) + ")";
return numBits - bitsRead;
}
public long numBits() {
return numBits;
}
/**
* Indicates whether or not this bit stream's current bit is located on a
* byte boundary.
*/
public boolean aligned() {
assert bitsCached < Byte.SIZE : "bitsCached(" + bitsCached + ") > " + (Byte.SIZE - 1);
return bitsCached == 0;
}
/**
* Returns a byte aligned view of this bit stream's content. This method
* should be called when multiple successive byte aligned operations are
* required. Returning to the bit stream state can be done via
* {@link ByteInput#unalign()}.
*
* @see ByteInput#unalign()
*/
public ByteInput align() {
// consume cache if bits remaining
assert bitsCached < Byte.SIZE : "bitsCached(" + bitsCached + ") > " + (Byte.SIZE - 1);
if (bitsCached > 0) {
bitsRead = Math.min(numBits, bitsRead + bitsCached);
clearCache();
}
assert bitsRead <= numBits : "bitsRead(" + bitsRead + ") > numBits(" + numBits + ")";
return byteInput;
}
/**
* Reads a slice of this bit stream's sub-region starting at the current
* bit and increases the bits read by the size of the new slice (= numBits).
*/
public BitInput readSlice(long numBits) {
// since this shouldn't go more than 1 level deep, can also generate a new
// ByteInput with a new BitInput if allowing align
if (numBits == 0) return ByteInput.emptyByteInput().unalign();
if (numBits < 0) throw new IllegalArgumentException("numBits(" + numBits + ") < " + 0);
if (bitsRead + numBits > this.numBits) {
throw new IllegalArgumentException(
"bitsRead(" + bitsRead + ") + sliceBits(" + numBits + ") > numBits(" + this.numBits + ")");
}
assert bitsCached < Byte.SIZE : "bitsCached(" + bitsCached + ") > " + (Byte.SIZE - 1);
// length should include the last byte that bits belong (round to ceil)
final long numBytes = (numBits - bitsCached + Byte.SIZE - 1) / Byte.SIZE;
final ByteInput byteInput = this.byteInput.readSlice(numBytes);
return byteInput.bitInput(new BitInput(byteInput, bitsCached, cache, numBits));
}
/**
* Skips <i>n</i> bits by discarding them.
*/
public BitInput skip(long bits) {
public BitInput skipBits(long bits) {
if (bits < 0) throw new IllegalArgumentException("bits(" + bits + ") < " + 0);
if (bits == 0) return this;
// aligns bit stream for multi-byte skipping
assert bitsCached < Byte.SIZE : "bitsCached(" + bitsCached + ") > " + (Byte.SIZE - 1);
if (bits >= bitsCached) {
bits -= bitsCached;
align();
}
// skips multiple bytes
final long startingBitsRead = bitsRead;
final long bytes = bits / Byte.SIZE;
assert bytes <= Integer.MAX_VALUE : "bytes(" + bytes + ") > Integer.MAX_VALUE";
if (bytes > 0) align((int) bytes);
if (bytes > 0) align().skipBytes((int) bytes);
// skips overflow bits
final long overflowBits = (startingBitsRead + bits) - bitsRead;
// checks single byte, multi-byte and expected max value
assert bytes != 0 || overflowBits < Byte.SIZE : "overflowBits(" + overflowBits + ") > " + (Byte.SIZE - 1);
@ -109,95 +182,128 @@ public class BitInput {
return this;
}
/**
* Aligns the bit stream to the nearest byte boundary, or not at all if it is
* already at one.
*
* @see #align(int)
*/
public BitInput align() {
return align(0);
}
/**
* Aligns the bit stream to the <i>nth</i> byte boundary. Alignment will
* behave as follows:
* <ul>
* <li>cache will always be erased</li>
* <li>{@code cached bits = 0} will consume {@code 0} bytes</li>
* <li>{@code cached bits > 0} will consume {@code 1} byte</li>
* </ul>
*/
public BitInput align(int bytes) {
if (bytes < 0) throw new IllegalArgumentException("bytes(" + bytes + ") < " + 0);
// consume cache if bits remaining
assert bitsCached < Byte.SIZE : "bitsCached(" + bitsCached + ") > " + (Byte.SIZE - 1);
if (bitsCached > 0) {
bitsRead = Math.min(numBits, bitsRead + bitsCached);
if (bytes > 0) bytes--;
clearCache();
long incrementBitsRead(long bits) {
if ((bitsRead += bits) > numBits) {
bitsRead = numBits;
throw new EndOfInput();
}
if (bytes > 0) {
bitsRead = Math.min(numBits, bitsRead + (bytes * Byte.SIZE));
buffer.skipBytes(bytes);
return bitsRead;
}
long decrementBitsRead(long bits) {
if ((bitsRead -= bits) < 0) {
assert false : "bitsRead(" + bitsRead + ") < " + 0;
bitsRead = 0;
}
assert bitsRead <= numBits : "bitsRead(" + bitsRead + ") > numBits(" + numBits + ")";
return this;
return bitsRead;
}
/**
* Returns whether or not the bit stream is aligned on a byte boundary.
*/
public boolean isAligned() {
assert bitsCached < Byte.SIZE : "bitsCached(" + bitsCached + ") > " + (Byte.SIZE - 1);
return bitsCached == 0;
}
/**
* Consumes bits until the specified sequence of bytes are encountered. After
* this method executes, position will be such that the next read operation
* is at the first byte in the signature. This method will align the bit
* stream to the byte boundary according to {@link #align()}.
* Reads up to {@value #MAX_UNSIGNED_BITS} bits as unsigned and casts the
* result into a {@code long}.
* <p/>
* <b>Precondition:</b> {@code signature.length == 2}.
*
* @see #align()
* @see #skip(long)
* <p>{@code bits} should be in [0, {@value #MAX_UNSIGNED_BITS}].
* <p>Reading {@code 0} bits will always return {@code 0}.
*/
public BitInput skipUntil(byte[] signature) {
if (signature.length != 2) {
throw new IllegalArgumentException(
"signature.length(" + signature.length + ") != " + 2 + ": " + DebugUtils.toByteArray(signature));
long readUnsigned(int bits) {
assert bits >= 0 : "bits(" + bits + ") < " + 0;
assert bits < Long.SIZE : "bits(" + bits + ") > " + (Long.SIZE - 1);
if (bits <= 0) return 0;
incrementBitsRead(bits);
ensureCache(bits);
return bitsCached < bits
? readCacheSafe(bits)
: readCacheUnsafe(bits);
}
/**
* Ensures {@link #cache} contains at least <i>n</i> bits, up to
* {@value #MAX_SAFE_CACHED_BITS} bits due to overflow.
*/
private int ensureCache(int bits) {
assert bits > 0 : "bits(" + bits + ") < " + 1;
assert bits < Long.SIZE : "bits(" + bits + ") > " + (Long.SIZE - 1);
while (bitsCached < bits && bitsCached <= MAX_SAFE_CACHED_BITS) {
final long nextByte = byteInput._read8u();
cache |= (nextByte << bitsCached);
bitsCached += Byte.SIZE;
}
align();
final byte fb0 = signature[0];
final byte fb1 = signature[1];
byte b0, b1;
b1 = read8();
for (;;) {
b0 = b1;
b1 = read8();
if (b0 == fb0 && b1 == fb1) {
buffer.readerIndex(buffer.readerIndex() - signature.length);
bitsRead -= ((long) signature.length * Byte.SIZE);
assert bitsRead >= 0 : "bitsRead(" + bitsRead + ") < " + 0;
assert bytesRemaining() >= signature.length
: "bytesRemaining(" + bytesRemaining() + ") < signature.length(" + signature.length + ")";
break;
}
}
return bitsCached;
}
// TODO: support dynamic signature lengths
// create a byte[] of size signature.length
// use as a circular buffer with each read byte incrementing index and then going back to
// 0 when signature.length is reached. Comparisons will need to be index..length
// and 0..index (and 0..length in case where index == 0)
/**
* Reads <i>n</i> bits from {@link #cache}, consuming the next byte in the
* underlying byte stream.
* <p/>
* This function asserts that {@link #cache} would have overflowed if
* <i>n</i> bits were read from the underlying byte stream and thus reads
* the next byte accounting for this case.
*/
private long readCacheSafe(int bits) {
assert bits > 0 : "bits(" + bits + ") < " + 1;
assert bits < Long.SIZE : "bits(" + bits + ") > " + (Long.SIZE - 1);
final int bitsToAddCount = bits - bitsCached;
final int overflowBits = Byte.SIZE - bitsToAddCount;
final long nextByte = byteInput._read8u();
long bitsToAdd = nextByte & MASKS[bitsToAddCount];
cache |= (bitsToAdd << bitsCached);
final long overflow = (nextByte >>> bitsToAddCount) & MASKS[overflowBits];
final long bitsOut = bitsCached & MASKS[bits];
cache = overflow;
bitsCached = overflowBits;
return bitsOut;
}
return this;
/**
* Reads <i>n</i> bits from {@link #cache}.
* <p/>
* This function asserts {@link #cache} contains at least <i>n</i> bits.
*/
private long readCacheUnsafe(int bits) {
assert bits > 0 : "bits(" + bits + ") < " + 1;
assert bits < Long.SIZE : "bits(" + bits + ") > " + (Long.SIZE - 1);
final long bitsOut = cache & MASKS[bits];
cache >>>= bits;
bitsCached -= bits;
return bitsOut;
}
/**
* Reads up to {@value Long#SIZE} bits and sign extending the result as a
* {@code long}.
* <p/>
* <p>{@code bits} should be in [0, {@value Long#SIZE}].
* <p>Reading {@code 0} bits will always return {@code 0}.
*/
long readSigned(int bits) {
assert bits >= 0 : "bits(" + bits + ") < " + 0;
assert bits <= Long.SIZE : "bits(" + bits + ") > " + Long.SIZE;
if (bits <= 0) return 0;
if (bits == Long.SIZE) return _readRaw(Long.SIZE);
final int shift = Long.SIZE - bits;
assert shift > 0;
final long value = readUnsigned(bits);
return value << shift >> shift;
}
long _readRaw(int bits) {
assert bits > 0 : "bits(" + bits + ") <= " + 0;
assert bits <= Long.SIZE : "bits(" + bits + ") > " + Long.SIZE;
long lo = readUnsigned(Math.min(Integer.SIZE, bits));
long hi = readUnsigned(Math.max(bits - Integer.SIZE, 0));
return (hi << Integer.SIZE) | lo;
}
/**
* Reads up to {@value Long#SIZE} bits as a {@code long}. This method is
* intended to be used to read raw memory (i.e., flags).
*/
public long readRaw(int bits) {
BitConstraints.validate64(bits);
return _readRaw(bits);
}
/**
@ -246,6 +352,8 @@ public class BitInput {
/**
* Reads {@code 1} bit as a {@code boolean}.
*
* @see #read1()
*/
public boolean readBoolean() {
return read1() != 0;
@ -293,308 +401,100 @@ public class BitInput {
}
/**
* Reads up to {@value Long#SIZE} bits as a {@code long}. This method is
* intended to be used to read raw memory (i.e., flags).
*/
public long readRaw(int bits) {
BitConstraints.validate64(bits);
return _readRaw(bits);
}
/**
* @see #readRaw
*/
private long _readRaw(int bits) {
assert bits > 0 : "bits(" + bits + ") <= " + 0;
assert bits <= Long.SIZE : "bits(" + bits + ") > " + Long.SIZE;
long lo = readUnsigned(Math.min(Integer.SIZE, bits));
long hi = readUnsigned(Math.max(bits - Integer.SIZE, 0));
return (hi << Integer.SIZE) | lo;
}
/**
* Reads up to {@value Long#SIZE} bits and sign extending the result as a
* {@code long}.
* <p/>
* <p>{@code bits} should be in [0, {@value Long#SIZE}].
* <p>Reading {@code 0} bits will always return {@code 0}.
*/
long readSigned(int bits) {
assert bits >= 0 : "bits(" + bits + ") < " + 0;
assert bits <= Long.SIZE : "bits(" + bits + ") > " + Long.SIZE;
if (bits <= 0) return 0;
if (bits == Long.SIZE) return _readRaw(Long.SIZE);
final int shift = Long.SIZE - bits;
assert shift > 0;
final long value = readUnsigned(bits);
return value << shift >> shift;
}
/**
* Reads the next byte from the underlying byte stream, ignoring alignment.
*/
private short _read8u() {
try {
final short octet = buffer.readUnsignedByte();
assert 0 <= octet && octet < (1 << Byte.SIZE);
return octet;
} catch (IndexOutOfBoundsException t) {
throw new EndOfInput(t);
}
}
long incrementBitsRead(long bits) {
if ((bitsRead += bits) > numBits) {
bitsRead = bits;
throw new EndOfInput();
}
return bitsRead;
}
/**
* Reads up to {@value #MAX_UNSIGNED_BITS} bits as unsigned and casts the
* result into a {@code long}.
* <p/>
* <p>{@code bits} should be in [0, {@value #MAX_UNSIGNED_BITS}].
* <p>Reading {@code 0} bits will always return {@code 0}.
*/
long readUnsigned(int bits) {
assert bits >= 0 : "bits(" + bits + ") < " + 0;
assert bits < Long.SIZE : "bits(" + bits + ") > " + (Long.SIZE - 1);
if (bits <= 0) return 0;
incrementBitsRead(bits);
ensureCache(bits);
return bitsCached < bits
? readCacheSafe(bits)
: readCacheUnsafe(bits);
}
/**
* Ensures {@link #cache} contains at least <i>n</i> bits, up to
* {@value #MAX_SAFE_CACHED_BITS} bits due to overflow.
*/
private int ensureCache(int bits) {
assert bits > 0 : "bits(" + bits + ") < " + 1;
assert bits < Long.SIZE : "bits(" + bits + ") > " + (Long.SIZE - 1);
while (bitsCached < bits && bitsCached <= MAX_SAFE_CACHED_BITS) {
final long nextByte = _read8u();
cache |= (nextByte << bitsCached);
bitsCached += Byte.SIZE;
}
return bitsCached;
}
/**
* Reads <i>n</i> bits from {@link #cache}, consuming the next byte in the
* underlying byte stream.
* <p/>
* This function asserts that {@link #cache} would have overflowed if
* <i>n</i> bits were read from the underlying byte stream and thus reads
* the next byte accounting for this case.
*/
private long readCacheSafe(int bits) {
assert bits > 0 : "bits(" + bits + ") < " + 1;
assert bits < Long.SIZE : "bits(" + bits + ") > " + (Long.SIZE - 1);
final int bitsToAddCount = bits - bitsCached;
final int overflowBits = Byte.SIZE - bitsToAddCount;
final long nextByte = _read8u();
long bitsToAdd = nextByte & MASKS[bitsToAddCount];
cache |= (bitsToAdd << bitsCached);
final long overflow = (nextByte >>> bitsToAddCount) & MASKS[overflowBits];
final long bitsOut = bitsCached & MASKS[bits];
cache = overflow;
bitsCached = overflowBits;
return bitsOut;
}
/**
* Reads <i>n</i> bits from {@link #cache}.
* <p/>
* This function asserts {@link #cache} contains at least <i>n</i> bits.
*/
private long readCacheUnsafe(int bits) {
assert bits > 0 : "bits(" + bits + ") < " + 1;
assert bits < Long.SIZE : "bits(" + bits + ") > " + (Long.SIZE - 1);
final long bitsOut = cache & MASKS[bits];
cache >>>= bits;
bitsCached -= bits;
return bitsOut;
}
private void validateAlignment() {
if (!isAligned()) {
throw new IllegalStateException(
"no-args method called on unaligned stream!");
}
}
/**
* Reads an unsigned byte from the bit stream.
* <p/>
* <b>Precondition:</b> This stream must be byte aligned to use this method.
* Reads an unsigned byte.
*/
public short read8u() {
validateAlignment();
try {
incrementBitsRead(Byte.SIZE);
return buffer.readUnsignedByte();
} catch (IndexOutOfBoundsException t) {
throw new EndOfInput(t);
}
return read15u(Byte.SIZE);
}
/**
* Reads an unsigned 16-bit short integer from the bit stream.
* <p/>
* <b>Precondition:</b> This stream must be byte aligned to use this method.
* Reads an unsigned 16-bit short integer.
*/
public int read16u() {
validateAlignment();
try {
incrementBitsRead(Short.SIZE);
return buffer.readUnsignedShortLE();
} catch (IndexOutOfBoundsException t) {
throw new EndOfInput(t);
}
return read31u(Short.SIZE);
}
/**
* Reads an unsigned 32-bit integer from the bit stream.
* <p/>
* <b>Precondition:</b> This stream must be byte aligned to use this method.
* Reads an unsigned 32-bit integer.
*/
public long read32u() {
validateAlignment();
try {
incrementBitsRead(Integer.SIZE);
return buffer.readUnsignedIntLE();
} catch (IndexOutOfBoundsException t) {
throw new EndOfInput(t);
}
return read63u(Integer.SIZE);
}
/**
* Reads a byte from the bit stream.
* <p/>
* <b>Precondition:</b> This stream must be byte aligned to use this method.
* Reads a byte.
*/
public byte read8() {
validateAlignment();
try {
incrementBitsRead(Byte.SIZE);
return buffer.readByte();
} catch (IndexOutOfBoundsException t) {
throw new EndOfInput(t);
}
return read8(Byte.SIZE);
}
/**
* Reads a 16-bit short integer from the bit stream.
* <p/>
* <b>Precondition:</b> This stream must be byte aligned to use this method.
* Reads a 16-bit short integer.
*/
public short read16() {
validateAlignment();
try {
incrementBitsRead(Short.SIZE);
return buffer.readShortLE();
} catch (IndexOutOfBoundsException t) {
throw new EndOfInput(t);
}
return read16(Short.SIZE);
}
/**
* Reads a 32-bit integer from the bit stream.
* <p/>
* <b>Precondition:</b> This stream must be byte aligned to use this method.
* Reads a 32-bit integer.
*/
public int read32() {
validateAlignment();
try {
incrementBitsRead(Integer.SIZE);
return buffer.readIntLE();
} catch (IndexOutOfBoundsException t) {
throw new EndOfInput(t);
}
return read32(Integer.SIZE);
}
/**
* Reads a 64-bit long integer from the bit stream.
* <p/>
* <b>Precondition:</b> This stream must be byte aligned to use this method.
* Reads a 64-bit long integer.
*/
public long read64() {
validateAlignment();
try {
incrementBitsRead(Long.SIZE);
return buffer.readLongLE();
} catch (IndexOutOfBoundsException t) {
throw new EndOfInput(t);
}
return read64(Long.SIZE);
}
/**
* Reads <i>n</i> bytes from the bit stream into a created byte array.
* <p/>
* <b>Precondition:</b> This stream must be byte aligned to use this method.
*
* @see #readBytes(byte[])
* @see #readBytes(byte[], int, int)
*/
public byte[] readBytes(int len) {
validateAlignment();
byte[] dst = new byte[len];
readBytes(dst);
return dst;
}
// /**
// * @deprecated unaligned reads not supported!
// * use {@code align().readBytes(int)} instead!
// * <pre>{@link #align()} {@link ByteInput#readBytes(int)}</pre>
// */
// @Deprecated
// public byte[] readBytes(int len) {
// throw new UnsupportedOperationException("use align().readBytes(int) instead!");
// }
// /**
// * @deprecated unaligned reads not supported!
// * use {@code align().readBytes(byte[])} instead!
// * <pre>{@link #align()} {@link ByteInput#readBytes(byte[])}</pre>
// */
// @Deprecated
// public byte[] readBytes(byte[] dst) {
// throw new UnsupportedOperationException("use align().readBytes(byte[]) instead!");
// }
// /**
// * @deprecated unaligned reads not supported!
// * use {@code align().readBytes(byte[], int, int)} instead!
// * <pre>{@link #align()} {@link ByteInput#readBytes(byte[], int, int)}</pre>
// */
// @Deprecated
// public byte[] readBytes(byte[] dst, int dstOffset, int len) {
// throw new UnsupportedOperationException("use align().readBytes(byte[],int,int) instead!");
// }
/**
* Reads <i>n</i> bytes from the bit stream into the specified byte array.
* <p/>
* <b>Precondition:</b> This stream must be byte aligned to use this method.
*
* @see #readBytes(int)
* @see #readBytes(byte[], int, int)
*/
public void readBytes(byte[] dst) {
validateAlignment();
readBytes(dst, 0, dst.length);
}
/**
* Reads <i>n</i> bytes from the bit stream into the specified byte array.
* <p/>
* <b>Precondition:</b> This stream must be byte aligned to use this method.
*
* @see #readBytes(int)
* @see #readBytes(byte[])
*/
public void readBytes(byte[] dst, int dstOffset, int len) {
validateAlignment();
align();
assert bitsCached == 0;
incrementBitsRead((long) len * Byte.SIZE);
buffer.readBytes(dst, dstOffset, len);
}
/**
* Reads <i>n</i> bytes from the bit stream and constructs a String.
* Reads <i>n</i> bytes from the bit stream and constructs a string.
*/
public String readString(int len) {
return readString(len, Byte.SIZE, false);
}
/**
* Reads <i>n</i> characters of size {@code bits} and constructs a String.
* Reads <i>n</i> characters of size {@code bits} and constructs a string.
*
* @param len number of characters to read
* @param bits size of each character ({@code 7} or {@code 8})
* @param nullTerminate {@code true} to stop reading at {@code \0}, otherwise
* {@code len} characters will be read
* @param nullTerminated {@code true} to stop reading at {@code \0}, otherwise
* {@code len} characters will be read (variable-width string)
*/
public String readString(int len, int bits, boolean nullTerminate) {
public String readString(int len, int bits, boolean nullTerminated) {
if (len < 0) throw new IllegalArgumentException("len(" + len + ") < " + 0);
BitConstraints.validateAscii(bits);
@ -602,7 +502,7 @@ public class BitInput {
final byte[] dst = new byte[len];
for (int i = 0; i < len; i++) {
final byte b = dst[i] = (byte) readUnsigned(bits);
if (nullTerminate && b == '\0') break;
if (nullTerminated && b == '\0') break;
}
return new String(dst);

View File

@ -1,4 +1,4 @@
package com.riiablo.io.nio;
package com.riiablo.io;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;

View File

@ -1,67 +0,0 @@
package com.riiablo.io.nio;
class BitConstraints {
private BitConstraints() {}
private static int _validateSize(int min, int max, int bits) {
assert min >= 0 : "min(" + min + ") < " + 0 + "!";
assert max > 0 : "max(" + max + ") <= " + 0;
assert min <= max : "min(" + min + ") > max(" + max + ")";
if (bits < min) {
throw new IllegalArgumentException("bits(" + bits + ") < " + min);
}
if (bits > max) {
throw new IllegalArgumentException("bits(" + bits + ") > " + max);
}
return bits;
}
private static int validateSizeU(int max, int bits) {
return _validateSize(0, max - 1, bits);
}
public static int validate7u(int bits) {
return validateSizeU(Byte.SIZE, bits);
}
public static int validate15u(int bits) {
return validateSizeU(Short.SIZE, bits);
}
public static int validate31u(int bits) {
return validateSizeU(Integer.SIZE, bits);
}
public static int validate63u(int bits) {
return validateSizeU(Long.SIZE, bits);
}
private static int validateSize(int max, int bits) {
return _validateSize(0, max, bits);
}
public static int validate8(int bits) {
return validateSize(Byte.SIZE, bits);
}
public static int validate16(int bits) {
return validateSize(Short.SIZE, bits);
}
public static int validate32(int bits) {
return validateSize(Integer.SIZE, bits);
}
public static int validate64(int bits) {
return validateSize(Long.SIZE, bits);
}
public static boolean isUnsigned(long value, int size) {
assert 0 < size && size <= Long.SIZE;
return (value & (1 << (size - 1))) == 0;
}
public static int validateAscii(int bits) {
return _validateSize(Byte.SIZE - 1, Byte.SIZE, bits);
}
}

View File

@ -1,510 +0,0 @@
package com.riiablo.io.nio;
import org.apache.commons.lang3.StringUtils;
/**
* Wraps a {@link ByteInput} to support reading sequences of bits and
* supporting {@link #align() re-aligning} the byte stream to read sequences of
* {@link ByteInput bytes}. All read functions will return results in little
* endian byte order.
*
* @see BitInput
* @see #align()
*/
// TODO: improve placeholder documentation
public class BitInput {
public static BitInput wrap(byte[] bytes) {
return ByteInput.wrap(bytes).unalign();
}
private static final int MAX_ULONG_BITS = Long.SIZE - 1;
private static final int MAX_UINT_BITS = Integer.SIZE - 1;
private static final int MAX_USHORT_BITS = Short.SIZE - 1;
private static final int MAX_UBYTE_BITS = Byte.SIZE - 1;
private static final int MAX_UNSIGNED_BITS = MAX_ULONG_BITS;
private static final int MAX_SAFE_CACHED_BITS = Long.SIZE - Byte.SIZE;
private static final long[] MASKS = new long[Long.SIZE];
static {
for (int i = 1; i < Long.SIZE; i++) {
MASKS[i] = (MASKS[i - 1] << 1) + 1;
}
}
private final ByteInput byteInput;
private final long numBits;
private long bitsRead;
private int bitsCached;
private long cache;
/**
* Constructs a BitInput instance at the start of the byteInput with
* {@code numBits} including all bits within {@code byteInput}.
*/
BitInput(ByteInput byteInput) {
this(byteInput, 0, 0L, (long) byteInput.bytesRemaining() * Byte.SIZE);
}
/**
* Constructs a BitInput instance with an initial state. This is typically
* done when the BitInput is created as the child of another BitInput.
*/
private BitInput(ByteInput byteInput, int bitsCached, long cache, long numBits) {
this.byteInput = byteInput;
this.bitsCached = bitsCached;
this.cache = cache;
this.numBits = numBits;
}
ByteInput byteInput() {
return byteInput;
}
public int bytesRead() {
return byteInput.bytesRead();
}
public int bytesRemaining() {
return byteInput.bytesRemaining();
}
public int numBytes() {
return byteInput.numBytes();
}
public int bitsCached() {
return bitsCached;
}
public long cache() {
return cache;
}
void clearCache() {
bitsCached = 0;
cache = 0L;
}
public long bitsRead() {
return bitsRead;
}
public long bitsRemaining() {
assert (numBits - bitsRead) == (bitsCached + ((long) bytesRemaining() * Byte.SIZE))
: "actual(" + (numBits - bitsRead) + ") != expected(" + (bitsCached + ((long) bytesRemaining() * Byte.SIZE)) + ")";
return numBits - bitsRead;
}
public long numBits() {
return numBits;
}
/**
* Indicates whether or not this bit stream's current bit is located on a
* byte boundary.
*/
public boolean aligned() {
assert bitsCached < Byte.SIZE : "bitsCached(" + bitsCached + ") > " + (Byte.SIZE - 1);
return bitsCached == 0;
}
/**
* Returns a byte aligned view of this bit stream's content. This method
* should be called when multiple successive byte aligned operations are
* required. Returning to the bit stream state can be done via
* {@link ByteInput#unalign()}.
*
* @see ByteInput#unalign()
*/
public ByteInput align() {
// consume cache if bits remaining
assert bitsCached < Byte.SIZE : "bitsCached(" + bitsCached + ") > " + (Byte.SIZE - 1);
if (bitsCached > 0) {
bitsRead = Math.min(numBits, bitsRead + bitsCached);
clearCache();
}
assert bitsRead <= numBits : "bitsRead(" + bitsRead + ") > numBits(" + numBits + ")";
return byteInput;
}
/**
* Reads a slice of this bit stream's sub-region starting at the current
* bit and increases the bits read by the size of the new slice (= numBits).
*/
public BitInput readSlice(long numBits) {
// since this shouldn't go more than 1 level deep, can also generate a new
// ByteInput with a new BitInput if allowing align
if (numBits == 0) return ByteInput.emptyByteInput().unalign();
if (numBits < 0) throw new IllegalArgumentException("numBits(" + numBits + ") < " + 0);
if (bitsRead + numBits > this.numBits) {
throw new IllegalArgumentException(
"bitsRead(" + bitsRead + ") + sliceBits(" + numBits + ") > numBits(" + this.numBits + ")");
}
assert bitsCached < Byte.SIZE : "bitsCached(" + bitsCached + ") > " + (Byte.SIZE - 1);
// length should include the last byte that bits belong (round to ceil)
final long numBytes = (numBits - bitsCached + Byte.SIZE - 1) / Byte.SIZE;
final ByteInput byteInput = this.byteInput.readSlice(numBytes);
return byteInput.bitInput(new BitInput(byteInput, bitsCached, cache, numBits));
}
/**
* Skips <i>n</i> bits by discarding them.
*/
public BitInput skipBits(long bits) {
if (bits < 0) throw new IllegalArgumentException("bits(" + bits + ") < " + 0);
if (bits == 0) return this;
// aligns bit stream for multi-byte skipping
assert bitsCached < Byte.SIZE : "bitsCached(" + bitsCached + ") > " + (Byte.SIZE - 1);
if (bits >= bitsCached) {
bits -= bitsCached;
align();
}
// skips multiple bytes
final long startingBitsRead = bitsRead;
final long bytes = bits / Byte.SIZE;
assert bytes <= Integer.MAX_VALUE : "bytes(" + bytes + ") > Integer.MAX_VALUE";
if (bytes > 0) align().skipBytes((int) bytes);
// skips overflow bits
final long overflowBits = (startingBitsRead + bits) - bitsRead;
// checks single byte, multi-byte and expected max value
assert bytes != 0 || overflowBits < Byte.SIZE : "overflowBits(" + overflowBits + ") > " + (Byte.SIZE - 1);
assert bytes == 0 || overflowBits < Short.SIZE : "overflowBits(" + overflowBits + ") > " + (Short.SIZE - 1);
assert overflowBits < Short.SIZE : "overflowBits(" + overflowBits + ") > " + (Short.SIZE - 1);
if (overflowBits > 0) _readRaw((int) overflowBits);
return this;
}
long incrementBitsRead(long bits) {
if ((bitsRead += bits) > numBits) {
bitsRead = numBits;
throw new EndOfInput();
}
return bitsRead;
}
long decrementBitsRead(long bits) {
if ((bitsRead -= bits) < 0) {
assert false : "bitsRead(" + bitsRead + ") < " + 0;
bitsRead = 0;
}
return bitsRead;
}
/**
* Reads up to {@value #MAX_UNSIGNED_BITS} bits as unsigned and casts the
* result into a {@code long}.
* <p/>
* <p>{@code bits} should be in [0, {@value #MAX_UNSIGNED_BITS}].
* <p>Reading {@code 0} bits will always return {@code 0}.
*/
long readUnsigned(int bits) {
assert bits >= 0 : "bits(" + bits + ") < " + 0;
assert bits < Long.SIZE : "bits(" + bits + ") > " + (Long.SIZE - 1);
if (bits <= 0) return 0;
incrementBitsRead(bits);
ensureCache(bits);
return bitsCached < bits
? readCacheSafe(bits)
: readCacheUnsafe(bits);
}
/**
* Ensures {@link #cache} contains at least <i>n</i> bits, up to
* {@value #MAX_SAFE_CACHED_BITS} bits due to overflow.
*/
private int ensureCache(int bits) {
assert bits > 0 : "bits(" + bits + ") < " + 1;
assert bits < Long.SIZE : "bits(" + bits + ") > " + (Long.SIZE - 1);
while (bitsCached < bits && bitsCached <= MAX_SAFE_CACHED_BITS) {
final long nextByte = byteInput._read8u();
cache |= (nextByte << bitsCached);
bitsCached += Byte.SIZE;
}
return bitsCached;
}
/**
* Reads <i>n</i> bits from {@link #cache}, consuming the next byte in the
* underlying byte stream.
* <p/>
* This function asserts that {@link #cache} would have overflowed if
* <i>n</i> bits were read from the underlying byte stream and thus reads
* the next byte accounting for this case.
*/
private long readCacheSafe(int bits) {
assert bits > 0 : "bits(" + bits + ") < " + 1;
assert bits < Long.SIZE : "bits(" + bits + ") > " + (Long.SIZE - 1);
final int bitsToAddCount = bits - bitsCached;
final int overflowBits = Byte.SIZE - bitsToAddCount;
final long nextByte = byteInput._read8u();
long bitsToAdd = nextByte & MASKS[bitsToAddCount];
cache |= (bitsToAdd << bitsCached);
final long overflow = (nextByte >>> bitsToAddCount) & MASKS[overflowBits];
final long bitsOut = bitsCached & MASKS[bits];
cache = overflow;
bitsCached = overflowBits;
return bitsOut;
}
/**
* Reads <i>n</i> bits from {@link #cache}.
* <p/>
* This function asserts {@link #cache} contains at least <i>n</i> bits.
*/
private long readCacheUnsafe(int bits) {
assert bits > 0 : "bits(" + bits + ") < " + 1;
assert bits < Long.SIZE : "bits(" + bits + ") > " + (Long.SIZE - 1);
final long bitsOut = cache & MASKS[bits];
cache >>>= bits;
bitsCached -= bits;
return bitsOut;
}
/**
* Reads up to {@value Long#SIZE} bits and sign extending the result as a
* {@code long}.
* <p/>
* <p>{@code bits} should be in [0, {@value Long#SIZE}].
* <p>Reading {@code 0} bits will always return {@code 0}.
*/
long readSigned(int bits) {
assert bits >= 0 : "bits(" + bits + ") < " + 0;
assert bits <= Long.SIZE : "bits(" + bits + ") > " + Long.SIZE;
if (bits <= 0) return 0;
if (bits == Long.SIZE) return _readRaw(Long.SIZE);
final int shift = Long.SIZE - bits;
assert shift > 0;
final long value = readUnsigned(bits);
return value << shift >> shift;
}
long _readRaw(int bits) {
assert bits > 0 : "bits(" + bits + ") <= " + 0;
assert bits <= Long.SIZE : "bits(" + bits + ") > " + Long.SIZE;
long lo = readUnsigned(Math.min(Integer.SIZE, bits));
long hi = readUnsigned(Math.max(bits - Integer.SIZE, 0));
return (hi << Integer.SIZE) | lo;
}
/**
* Reads up to {@value Long#SIZE} bits as a {@code long}. This method is
* intended to be used to read raw memory (i.e., flags).
*/
public long readRaw(int bits) {
BitConstraints.validate64(bits);
return _readRaw(bits);
}
/**
* Reads up to {@value #MAX_UBYTE_BITS} bits as unsigned and casts the result
* into a {@code byte}.
*/
public byte read7u(int bits) {
BitConstraints.validate7u(bits);
final byte value = (byte) readUnsigned(bits);
assert BitConstraints.isUnsigned(value, Byte.SIZE);
return value;
}
/**
* Reads up to {@value #MAX_USHORT_BITS} bits as unsigned and casts the
* result into a {@code short}.
*/
public short read15u(int bits) {
BitConstraints.validate15u(bits);
final short value = (short) readUnsigned(bits);
assert BitConstraints.isUnsigned(value, Short.SIZE);
return value;
}
/**
* Reads up to {@value #MAX_UINT_BITS} bits as unsigned and casts the result
* into a {@code int}.
*/
public int read31u(int bits) {
BitConstraints.validate31u(bits);
final int value = (int) readUnsigned(bits);
assert BitConstraints.isUnsigned(value, Integer.SIZE);
return value;
}
/**
* Reads up to {@value #MAX_ULONG_BITS} bits as unsigned and casts the result
* into a {@code long}.
*/
public long read63u(int bits) {
BitConstraints.validate63u(bits);
final long value = (long) readUnsigned(bits);
assert BitConstraints.isUnsigned(value, Long.SIZE);
return value;
}
/**
* Reads {@code 1} bit as a {@code boolean}.
*
* @see #read1()
*/
public boolean readBoolean() {
return read1() != 0;
}
/**
* Reads {@code 1} bit as a {@code byte}.
*/
public byte read1() {
final byte value = read7u(1);
assert (value & ~1) == 0;
return value;
}
/**
* Reads up to {@value Byte#SIZE} bits as a sign-extended {@code byte}.
*/
public byte read8(int bits) {
BitConstraints.validate8(bits);
return (byte) readSigned(bits);
}
/**
* Reads up to {@value Short#SIZE} bits as a sign-extended {@code short}.
*/
public short read16(int bits) {
BitConstraints.validate16(bits);
return (short) readSigned(bits);
}
/**
* Reads up to {@value Integer#SIZE} bits as a sign-extended {@code int}.
*/
public int read32(int bits) {
BitConstraints.validate32(bits);
return (int) readSigned(bits);
}
/**
* Reads up to {@value Long#SIZE} bits as a sign-extended {@code long}.
*/
public long read64(int bits) {
BitConstraints.validate64(bits);
return readSigned(bits);
}
/**
* Reads an unsigned byte.
*/
public short read8u() {
return read15u(Byte.SIZE);
}
/**
* Reads an unsigned 16-bit short integer.
*/
public int read16u() {
return read31u(Short.SIZE);
}
/**
* Reads an unsigned 32-bit integer.
*/
public long read32u() {
return read63u(Integer.SIZE);
}
/**
* Reads a byte.
*/
public byte read8() {
return read8(Byte.SIZE);
}
/**
* Reads a 16-bit short integer.
*/
public short read16() {
return read16(Short.SIZE);
}
/**
* Reads a 32-bit integer.
*/
public int read32() {
return read32(Integer.SIZE);
}
/**
* Reads a 64-bit long integer.
*/
public long read64() {
return read64(Long.SIZE);
}
// /**
// * @deprecated unaligned reads not supported!
// * use {@code align().readBytes(int)} instead!
// * <pre>{@link #align()} {@link ByteInput#readBytes(int)}</pre>
// */
// @Deprecated
// public byte[] readBytes(int len) {
// throw new UnsupportedOperationException("use align().readBytes(int) instead!");
// }
// /**
// * @deprecated unaligned reads not supported!
// * use {@code align().readBytes(byte[])} instead!
// * <pre>{@link #align()} {@link ByteInput#readBytes(byte[])}</pre>
// */
// @Deprecated
// public byte[] readBytes(byte[] dst) {
// throw new UnsupportedOperationException("use align().readBytes(byte[]) instead!");
// }
// /**
// * @deprecated unaligned reads not supported!
// * use {@code align().readBytes(byte[], int, int)} instead!
// * <pre>{@link #align()} {@link ByteInput#readBytes(byte[], int, int)}</pre>
// */
// @Deprecated
// public byte[] readBytes(byte[] dst, int dstOffset, int len) {
// throw new UnsupportedOperationException("use align().readBytes(byte[],int,int) instead!");
// }
/**
* Reads <i>n</i> bytes from the bit stream and constructs a string.
*/
public String readString(int len) {
return readString(len, Byte.SIZE, false);
}
/**
* Reads <i>n</i> characters of size {@code bits} and constructs a string.
*
* @param len number of characters to read
* @param bits size of each character ({@code 7} or {@code 8})
* @param nullTerminated {@code true} to stop reading at {@code \0}, otherwise
* {@code len} characters will be read (variable-width string)
*/
public String readString(int len, int bits, boolean nullTerminated) {
if (len < 0) throw new IllegalArgumentException("len(" + len + ") < " + 0);
BitConstraints.validateAscii(bits);
if (len == 0) return StringUtils.EMPTY;
final byte[] dst = new byte[len];
for (int i = 0; i < len; i++) {
final byte b = dst[i] = (byte) readUnsigned(bits);
if (nullTerminated && b == '\0') break;
}
return new String(dst);
}
}

View File

@ -1,13 +0,0 @@
package com.riiablo.io.nio;
public class EndOfInput extends RuntimeException {
private static final String DEFAULT_MESSAGE = "The end of the input has been reached!";
EndOfInput() {
super(DEFAULT_MESSAGE);
}
EndOfInput(Throwable cause) {
super(DEFAULT_MESSAGE, cause);
}
}

View File

@ -11,9 +11,15 @@ public class BitInputTest {
});
}
@Test
public void align_matches_unalign() {
BitInput b = newInstance();
Assert.assertEquals(b, b.align().unalign());
}
@Test
public void empty_bit_input_is_empty() {
BitInput b = BitInput.emptyBitInput();
BitInput b = ByteInput.emptyByteInput().unalign();
Assert.assertEquals(0, b.bitsRemaining());
Assert.assertEquals(0, b.bytesRemaining());
}
@ -23,7 +29,7 @@ public class BitInputTest {
BitInput b = newInstance();
try {
assert b.bitsRead() == 0;
b.skip(-1);
b.skipBits(-1);
} finally {
Assert.assertEquals(0L, b.bitsRead());
}
@ -33,9 +39,9 @@ public class BitInputTest {
public void skip_0_bits_aligned() {
BitInput b = newInstance();
long bitsRead = b.bitsRead();
assert b.isAligned();
b.skip(0);
Assert.assertTrue(b.isAligned());
assert b.aligned();
b.skipBits(0);
Assert.assertTrue(b.aligned());
Assert.assertEquals(bitsRead, b.bitsRead());
Assert.assertEquals(0, b.bitsCached());
Assert.assertEquals(0, b.cache());
@ -44,13 +50,13 @@ public class BitInputTest {
@Test
public void skip_0_bits_unaligned() {
BitInput b = newInstance();
b.skip(4);
b.skipBits(4);
long bitsRead = b.bitsRead();
int bitsCached = b.bitsCached();
long cache = b.cache();
assert !b.isAligned();
b.skip(0);
Assert.assertTrue(!b.isAligned());
assert !b.aligned();
b.skipBits(0);
Assert.assertTrue(!b.aligned());
Assert.assertEquals(bitsRead, b.bitsRead());
Assert.assertEquals(bitsCached, b.bitsCached());
Assert.assertEquals(cache, b.cache());
@ -59,8 +65,8 @@ public class BitInputTest {
@Test
public void skip_n_bits_aligned_to_aligned() {
BitInput b = newInstance();
assert b.isAligned();
b.skip(Byte.SIZE);
assert b.aligned();
b.skipBits(Byte.SIZE);
Assert.assertEquals(Byte.SIZE, b.bitsRead());
Assert.assertEquals(0, b.bitsCached());
Assert.assertEquals(0, b.cache());
@ -69,9 +75,9 @@ public class BitInputTest {
@Test
public void skip_n_bits_aligned_to_unaligned() {
BitInput b = newInstance();
assert b.isAligned();
b.skip(Byte.SIZE - 1);
Assert.assertTrue(!b.isAligned());
assert b.aligned();
b.skipBits(Byte.SIZE - 1);
Assert.assertTrue(!b.aligned());
Assert.assertEquals(Byte.SIZE - 1, b.bitsRead());
Assert.assertEquals(1, b.bitsCached());
Assert.assertEquals(0b1, b.cache());
@ -80,11 +86,11 @@ public class BitInputTest {
@Test
public void skip_n_bits_unaligned_to_aligned() {
BitInput b = newInstance();
b.skip(1);
b.skipBits(1);
assert b.bitsRead() == 1;
assert !b.isAligned();
b.skip(Byte.SIZE - 1);
Assert.assertTrue(b.isAligned());
assert !b.aligned();
b.skipBits(Byte.SIZE - 1);
Assert.assertTrue(b.aligned());
Assert.assertEquals(Byte.SIZE, b.bitsRead());
Assert.assertEquals(0, b.bitsCached());
Assert.assertEquals(0, b.cache());
@ -93,10 +99,10 @@ public class BitInputTest {
@Test
public void skip_n_bits_unaligned_to_unaligned() {
BitInput b = newInstance();
b.skip(1);
assert !b.isAligned();
b.skip(Byte.SIZE - 2);
Assert.assertTrue(!b.isAligned());
b.skipBits(1);
assert !b.aligned();
b.skipBits(Byte.SIZE - 2);
Assert.assertTrue(!b.aligned());
Assert.assertEquals(Byte.SIZE - 1, b.bitsRead());
Assert.assertEquals(1, b.bitsCached());
Assert.assertEquals(0b1, b.cache());
@ -105,8 +111,8 @@ public class BitInputTest {
@Test
public void skip_n_bits_aligned_to_aligned_multibyte() {
BitInput b = newInstance();
assert b.isAligned();
b.skip(Byte.SIZE + Byte.SIZE);
assert b.aligned();
b.skipBits(Byte.SIZE + Byte.SIZE);
Assert.assertEquals(Byte.SIZE + Byte.SIZE, b.bitsRead());
Assert.assertEquals(0, b.bitsCached());
Assert.assertEquals(0, b.cache());
@ -115,9 +121,9 @@ public class BitInputTest {
@Test
public void skip_n_bits_aligned_to_unaligned_multibyte() {
BitInput b = newInstance();
assert b.isAligned();
b.skip(Byte.SIZE + Byte.SIZE - 1);
Assert.assertTrue(!b.isAligned());
assert b.aligned();
b.skipBits(Byte.SIZE + Byte.SIZE - 1);
Assert.assertTrue(!b.aligned());
Assert.assertEquals(Byte.SIZE + Byte.SIZE - 1, b.bitsRead());
Assert.assertEquals(1, b.bitsCached());
Assert.assertEquals(0b1, b.cache());
@ -126,11 +132,11 @@ public class BitInputTest {
@Test
public void skip_n_bits_unaligned_to_aligned_multibyte() {
BitInput b = newInstance();
b.skip(1);
b.skipBits(1);
assert b.bitsRead() == 1;
assert !b.isAligned();
b.skip(Byte.SIZE + Byte.SIZE - 1);
Assert.assertTrue(b.isAligned());
assert !b.aligned();
b.skipBits(Byte.SIZE + Byte.SIZE - 1);
Assert.assertTrue(b.aligned());
Assert.assertEquals(Byte.SIZE + Byte.SIZE, b.bitsRead());
Assert.assertEquals(0, b.bitsCached());
Assert.assertEquals(0, b.cache());
@ -139,93 +145,15 @@ public class BitInputTest {
@Test
public void skip_n_bits_unaligned_to_unaligned_multibyte() {
BitInput b = newInstance();
b.skip(1);
assert !b.isAligned();
b.skip(Byte.SIZE);
Assert.assertTrue(!b.isAligned());
b.skipBits(1);
assert !b.aligned();
b.skipBits(Byte.SIZE);
Assert.assertTrue(!b.aligned());
Assert.assertEquals(Byte.SIZE + 1, b.bitsRead());
Assert.assertEquals(7, b.bitsCached());
Assert.assertEquals(0b1011111, b.cache());
}
@Test(expected = IllegalArgumentException.class)
public void align_neg_bytes_throws_IllegalArgumentException() {
BitInput b = newInstance();
try {
assert b.bitsRead() == 0;
b.align(-1);
} finally {
Assert.assertEquals(0L, b.bitsRead());
}
}
@Test
public void align_0_bytes_aligned() {
BitInput b = newInstance();
assert b.isAligned();
assert b.bitsCached() == 0;
b.align(0);
Assert.assertTrue(b.isAligned());
Assert.assertEquals(0, b.bitsRead());
Assert.assertEquals(0, b.bitsCached());
Assert.assertEquals(0, b.cache());
}
@Test
public void align_0_bytes_unaligned() {
BitInput b = newInstance();
b.skip(4);
assert !b.isAligned();
assert b.bitsCached() > 0;
b.align(0);
Assert.assertTrue(b.isAligned());
Assert.assertEquals(Byte.SIZE, b.bitsRead());
Assert.assertEquals(0, b.bitsCached());
Assert.assertEquals(0, b.cache());
}
@Test
public void align_1_byte_aligned() {
BitInput b = newInstance();
assert b.isAligned();
assert b.bitsCached() == 0;
b.align(1);
Assert.assertTrue(b.isAligned());
Assert.assertEquals(Byte.SIZE, b.bitsRead());
Assert.assertEquals(0, b.bitsCached());
Assert.assertEquals(0, b.cache());
}
@Test
public void align_1_byte_unaligned() {
BitInput b = newInstance();
b.skip(4);
assert !b.isAligned();
assert b.bitsCached() > 0;
b.align(1);
Assert.assertTrue(b.isAligned());
Assert.assertEquals(Byte.SIZE, b.bitsRead());
Assert.assertEquals(0, b.bitsCached());
Assert.assertEquals(0, b.cache());
}
@Test
public void readBytes_aligned() {
final byte[] signature = new byte[] {0x4A, 0x4D};
BitInput b = BitInput.wrap(new byte[] {
signature[0], signature[1], 0x10, 0x00, (byte) 0x80, 0x00, 0x65, 0x00, 0x04,
(byte) 0x82, 0x26, 0x76, 0x07, (byte) 0x82, 0x09, (byte) 0xD4,
(byte) 0xAA, 0x12, 0x03, 0x01, (byte) 0x80, 0x70, 0x01, 0x01,
(byte) 0x91, 0x03, 0x01, 0x04, 0x64, (byte) 0xFC, 0x07});
assert b.isAligned();
final byte[] bytesRead = b.readBytes(signature.length);
Assert.assertTrue(b.isAligned());
Assert.assertArrayEquals(signature, bytesRead); // signature
Assert.assertEquals(signature.length * Byte.SIZE, b.bitsRead());
Assert.assertEquals(0, b.bitsCached());
Assert.assertEquals(0, b.cache());
}
@Test
public void read_npc_data() {
BitInput b = BitInput.wrap(new byte[] {
@ -237,7 +165,7 @@ public class BitInputTest {
(byte) 0xFA, (byte) 0x7B, (byte) 0xE7, (byte) 0x18, 0x00, 0x00, 0x00, 0x00,
(byte) 0xDA, (byte) 0x79, (byte) 0xC7, (byte) 0x18, 0x00, 0x00, 0x00, 0x00,
(byte) 0x10, (byte) 0x51, (byte) 0xE3, (byte) 0x18, 0x00, 0x00, 0x00, 0x00});
Assert.assertArrayEquals(new byte[] {0x01, 0x77}, b.readBytes(2)); // signature
Assert.assertArrayEquals(new byte[] {0x01, 0x77}, b.align().readBytes(2)); // signature
Assert.assertEquals(52, b.readUnsigned(16)); // size
Assert.assertEquals(0x00000002_89A5AEACL, b.readRaw(64));
Assert.assertEquals(0x00000002_89A4BEACL, b.readRaw(64));
@ -259,7 +187,7 @@ public class BitInputTest {
(byte) 0xFA, (byte) 0x7B, (byte) 0xE7, (byte) 0x18, 0x00, 0x00, 0x00, 0x00,
(byte) 0xDA, (byte) 0x79, (byte) 0xC7, (byte) 0x18, 0x00, 0x00, 0x00, 0x00,
(byte) 0x10, (byte) 0x51, (byte) 0xE3, (byte) 0x18, 0x00, 0x00, 0x00, 0x00});
Assert.assertArrayEquals(new byte[]{0x01, 0x77}, b.readBytes(2)); // signature
Assert.assertArrayEquals(new byte[]{0x01, 0x77}, b.align().readBytes(2)); // signature
Assert.assertEquals(52, b.read16u()); // size
Assert.assertEquals(0x00000002_89A5AEACL, b.readRaw(64));
Assert.assertEquals(0x00000002_89A4BEACL, b.readRaw(64));
@ -277,10 +205,10 @@ public class BitInputTest {
(byte) 0x82, 0x26, 0x76, 0x07, (byte) 0x82, 0x09, (byte) 0xD4,
(byte) 0xAA, 0x12, 0x03, 0x01, (byte) 0x80, 0x70, 0x01, 0x01,
(byte) 0x91, 0x03, 0x01, 0x04, 0x64, (byte) 0xFC, 0x07});
Assert.assertArrayEquals(new byte[] {0x4A, 0x4D}, b.readBytes(2)); // signature
Assert.assertArrayEquals(new byte[] {0x4A, 0x4D}, b.align().readBytes(2)); // signature
Assert.assertEquals(0x00800010, b.readUnsigned(Integer.SIZE)); // flags
Assert.assertEquals(101, b.readUnsigned(8)); // version
b.skip(2); // unknown
b.skipBits(2); // unknown
Assert.assertEquals(0, b.readUnsigned(3)); // location
Assert.assertEquals(0, b.readUnsigned(4)); // body location
Assert.assertEquals(2, b.readUnsigned(4)); // grid x
@ -295,7 +223,7 @@ public class BitInputTest {
Assert.assertEquals(false, b.readBoolean()); // class only
Assert.assertEquals(0, b.readUnsigned(11)); // magic prefix
Assert.assertEquals(737, b.readUnsigned(11)); // magic suffix
b.skip(1); // unknown
b.skipBits(1); // unknown
Assert.assertEquals(32, b.readUnsigned(8)); // max durability
Assert.assertEquals(32, b.readUnsigned(9)); // durability
Assert.assertEquals(57, b.readUnsigned(9)); // poisonmindam
@ -316,10 +244,10 @@ public class BitInputTest {
(byte) 0x82, 0x26, 0x76, 0x07, (byte) 0x82, 0x09, (byte) 0xD4,
(byte) 0xAA, 0x12, 0x03, 0x01, (byte) 0x80, 0x70, 0x01, 0x01,
(byte) 0x91, 0x03, 0x01, 0x04, 0x64, (byte) 0xFC, 0x07});
Assert.assertArrayEquals(new byte[] {0x4A, 0x4D}, b.readBytes(2)); // signature
Assert.assertArrayEquals(new byte[] {0x4A, 0x4D}, b.align().readBytes(2)); // signature
Assert.assertEquals(0x00800010, b.readRaw(32)); // flags
Assert.assertEquals(101, b.read8u()); // version
b.skip(2); // unknown
b.skipBits(2); // unknown
Assert.assertEquals(0, b.read7u(3)); // location
Assert.assertEquals(0, b.read7u(4)); // body location
Assert.assertEquals(2, b.read7u(4)); // grid x
@ -334,7 +262,7 @@ public class BitInputTest {
Assert.assertEquals(false, b.readBoolean()); // class only
Assert.assertEquals(0, b.read15u(11)); // magic prefix
Assert.assertEquals(737, b.read15u(11)); // magic suffix
b.skip(1); // unknown
b.skipBits(1); // unknown
Assert.assertEquals(32, b.read15u(8)); // max durability
Assert.assertEquals(32, b.read15u(9)); // durability
Assert.assertEquals(57, b.read15u(9)); // poisonmindam