From 673b437c6158aa117b32117042846896210cd7b5 Mon Sep 17 00:00:00 2001 From: Collin Smith Date: Sat, 8 Aug 2020 01:07:28 -0700 Subject: [PATCH] Committing com.riiablo.io.nio experiment Replaces com.riiablo.io if I decide to go this route --- core/src/com/riiablo/io/nio/Aligned.java | 5 + .../src/com/riiablo/io/nio/AlignedReader.java | 22 + .../com/riiablo/io/nio/BitConstraints.java | 67 +++ core/src/com/riiablo/io/nio/BitInput.java | 400 ++++++++++++++++++ core/src/com/riiablo/io/nio/ByteInput.java | 243 +++++++++++ core/src/com/riiablo/io/nio/EndOfInput.java | 13 + .../com/riiablo/io/nio/UnalignedReader.java | 88 ++++ 7 files changed, 838 insertions(+) create mode 100644 core/src/com/riiablo/io/nio/Aligned.java create mode 100644 core/src/com/riiablo/io/nio/AlignedReader.java create mode 100644 core/src/com/riiablo/io/nio/BitConstraints.java create mode 100644 core/src/com/riiablo/io/nio/BitInput.java create mode 100644 core/src/com/riiablo/io/nio/ByteInput.java create mode 100644 core/src/com/riiablo/io/nio/EndOfInput.java create mode 100644 core/src/com/riiablo/io/nio/UnalignedReader.java diff --git a/core/src/com/riiablo/io/nio/Aligned.java b/core/src/com/riiablo/io/nio/Aligned.java new file mode 100644 index 00000000..bc73acb9 --- /dev/null +++ b/core/src/com/riiablo/io/nio/Aligned.java @@ -0,0 +1,5 @@ +package com.riiablo.io.nio; + +interface Aligned { + boolean aligned(); +} diff --git a/core/src/com/riiablo/io/nio/AlignedReader.java b/core/src/com/riiablo/io/nio/AlignedReader.java new file mode 100644 index 00000000..efa68819 --- /dev/null +++ b/core/src/com/riiablo/io/nio/AlignedReader.java @@ -0,0 +1,22 @@ +package com.riiablo.io.nio; + +interface AlignedReader extends Aligned { + int bytesRead(); + int bytesRemaining(); + int numBytes(); + + byte read8(); + short read16(); + int read32(); + long read64(); + + short read8u(); + int read16u(); + long read32u(); + + byte[] readBytes(int len); + byte[] readBytes(byte[] dst); + byte[] readBytes(byte[] dst, int dstOffset, int len); + + String readString(int len); +} diff --git a/core/src/com/riiablo/io/nio/BitConstraints.java b/core/src/com/riiablo/io/nio/BitConstraints.java new file mode 100644 index 00000000..acc523e1 --- /dev/null +++ b/core/src/com/riiablo/io/nio/BitConstraints.java @@ -0,0 +1,67 @@ +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); + } +} diff --git a/core/src/com/riiablo/io/nio/BitInput.java b/core/src/com/riiablo/io/nio/BitInput.java new file mode 100644 index 00000000..04cb75db --- /dev/null +++ b/core/src/com/riiablo/io/nio/BitInput.java @@ -0,0 +1,400 @@ +package com.riiablo.io.nio; + +import org.apache.commons.lang3.StringUtils; + +public class BitInput implements Aligned, AlignedReader, UnalignedReader { + + 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; + } + } + + final ByteInput byteInput; +// final ByteBuf buffer; + final long numBits; + long bitsRead; + + private int bitsCached; + private long cache; + + BitInput(ByteInput byteInput, int bitsCached, long cache, long numBits) { + this.byteInput = byteInput; +// this.buffer = byteInput.buffer; + this.bitsCached = bitsCached; + this.cache = cache; + this.numBits = numBits; + } + + @Override + public int bytesRead() { + return byteInput.bytesRead(); + } + + @Override + public int bytesRemaining() { + return byteInput.bytesRemaining(); + } + + @Override + public int numBytes() { + return byteInput.numBytes(); + } + + @Override + public int bitsCached() { + return bitsCached; + } + + @Override + public long cache() { + return cache; + } + + void clearCache() { + bitsCached = 0; + cache = 0L; + } + + @Override + public long bitsRead() { + return bitsRead; + } + + @Override + public long bitsRemaining() { + assert (numBits - bitsRead) == (bitsCached + ((long) bytesRemaining() * Byte.SIZE)) + : "actual(" + (numBits - bitsRead) + ") != expected(" + (bitsCached + ((long) bytesRemaining() * Byte.SIZE)) + ")"; + return numBits - bitsRead; + } + + @Override + public long numBits() { + return numBits; + } + + @Override + public boolean aligned() { + assert bitsCached < Byte.SIZE : "bitsCached(" + bitsCached + ") > " + (Byte.SIZE - 1); + return bitsCached == 0; + } + + 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; + } + + 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; + return byteInput.readSlice(numBytes, bitsCached, cache, numBits).unalign(); + } + + public BitInput discard(long bits) { + if (bits < 0) throw new IllegalArgumentException("bits(" + bits + ") < " + 0); + if (bits == 0) return this; + + final long startingBitsRead = bitsRead; + final long bytes = bits / Byte.SIZE; + assert bytes <= Integer.MAX_VALUE : "bytes(" + bytes + ") > Integer.MAX_VALUE"; + if (bytes > 0) align().discard((int) bytes); + + 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}. + *

+ *

{@code bits} should be in [0, {@value #MAX_UNSIGNED_BITS}]. + *

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 n 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 n bits from {@link #cache}, consuming the next byte in the + * underlying byte stream. + *

+ * This function asserts that {@link #cache} would have overflowed if + * n 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 n bits from {@link #cache}. + *

+ * This function asserts {@link #cache} contains at least n 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}. + *

+ *

{@code bits} should be in [0, {@value Long#SIZE}]. + *

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; + } + + @Override + public long readRaw(int bits) { + BitConstraints.validate64(bits); + return _readRaw(bits); + } + + @Override + public byte read7u(int bits) { + BitConstraints.validate7u(bits); + final byte value = (byte) readUnsigned(bits); + assert BitConstraints.isUnsigned(value, Byte.SIZE); + return value; + } + + @Override + public short read15u(int bits) { + BitConstraints.validate15u(bits); + final short value = (short) readUnsigned(bits); + assert BitConstraints.isUnsigned(value, Short.SIZE); + return value; + } + + @Override + public int read31u(int bits) { + BitConstraints.validate31u(bits); + final int value = (int) readUnsigned(bits); + assert BitConstraints.isUnsigned(value, Integer.SIZE); + return value; + } + + @Override + public long read63u(int bits) { + BitConstraints.validate63u(bits); + final long value = (long) readUnsigned(bits); + assert BitConstraints.isUnsigned(value, Long.SIZE); + return value; + } + + @Override + public boolean readBoolean() { + return read1() != 0; + } + + @Override + public byte read1() { + final byte value = read7u(1); + assert (value & ~1) == 0; + return value; + } + + @Override + public byte read8(int bits) { + BitConstraints.validate8(bits); + return (byte) readSigned(bits); + } + + @Override + public short read16(int bits) { + BitConstraints.validate16(bits); + return (short) readSigned(bits); + } + + @Override + public int read32(int bits) { + BitConstraints.validate32(bits); + return (int) readSigned(bits); + } + + @Override + public long read64(int bits) { + BitConstraints.validate64(bits); + return readSigned(bits); + } + + @Override + public short read8u() { + return read15u(Byte.SIZE); + } + + @Override + public int read16u() { + return read31u(Short.SIZE); + } + + @Override + public long read32u() { + return read63u(Integer.SIZE); + } + + @Override + public byte read8() { + return read8(Byte.SIZE); + } + + @Override + public short read16() { + return read16(Short.SIZE); + } + + @Override + public int read32() { + return read32(Integer.SIZE); + } + + @Override + public long read64() { + return read64(Long.SIZE); + } + + /** + * Aligns bit stream and reads from {@link #align()} + */ + @Override + public byte[] readBytes(int len) { + return align().readBytes(len); + } + + /** + * Aligns bit stream and reads from {@link #align()} + */ + @Override + public byte[] readBytes(byte[] dst) { + return align().readBytes(dst); + } + + /** + * Aligns bit stream and reads from {@link #align()} + */ + @Override + public byte[] readBytes(byte[] dst, int dstOffset, int len) { + return align().readBytes(dst, dstOffset, len); + } + + @Override + public String readString(int len) { + return readString(len, Byte.SIZE, false); + } + + @Override + 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); + } +} diff --git a/core/src/com/riiablo/io/nio/ByteInput.java b/core/src/com/riiablo/io/nio/ByteInput.java new file mode 100644 index 00000000..c5e554b9 --- /dev/null +++ b/core/src/com/riiablo/io/nio/ByteInput.java @@ -0,0 +1,243 @@ +package com.riiablo.io.nio; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.util.CharsetUtil; + +import com.riiablo.util.DebugUtils; + +public class ByteInput implements Aligned, AlignedReader { + private static final ByteInput EMPTY_BYTEINPUT = new ByteInput(Unpooled.EMPTY_BUFFER); + public static ByteInput emptyByteInput() { + return EMPTY_BYTEINPUT; + } + + public static ByteInput wrap(byte[] bytes) { + return bytes == null ? emptyByteInput() : new ByteInput(Unpooled.wrappedBuffer(bytes)); + } + + final BitInput bitInput; + final ByteBuf buffer; + + ByteInput(ByteBuf buffer) { + this(buffer, 0, 0L, (long) buffer.readableBytes() * Byte.SIZE); + } + + ByteInput(ByteBuf buffer, int bitsCached, long cache, long numBits) { + this.buffer = buffer; + this.bitInput = new BitInput(this, bitsCached, cache, numBits); + } + + @Override + public int bytesRead() { + return buffer.readerIndex(); + } + + @Override + public int bytesRemaining() { + return buffer.readableBytes(); + } + + @Override + public int numBytes() { + return buffer.capacity(); + } + + @Override + public boolean aligned() { + return bitInput.aligned(); + } + + public BitInput unalign() { + return bitInput; + } + + public ByteInput discard(int bytes) { + buffer.skipBytes(bytes); + return this; + } + + /** + * Consumes bytes 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. + *

+ * Precondition: {@code signature.length == 2}. + * + * @see #discard(int) + */ + public ByteInput discardUntil(byte[] signature) { + assert aligned() : "not aligned"; + if (signature.length != 2) { + throw new IllegalArgumentException( + "signature.length(" + signature.length + ") != " + 2 + ": " + DebugUtils.toByteArray(signature)); + } + + 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); + decrementBitsRead((long) signature.length * Byte.SIZE); + assert bytesRemaining() >= signature.length + : "bytesRemaining(" + bytesRemaining() + ") < signature.length(" + signature.length + ")"; + break; + } + } + + // 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) + + return this; + } + + public ByteInput readSlice(long numBytes) { + return readSlice(numBytes, 0, 0L, numBytes * Byte.SIZE); + } + + public ByteInput readSlice(long numBytes, int bitsCached, long cache, long numBits) { + assert numBytes <= Integer.MAX_VALUE : "ByteBuf only supports int length"; + final ByteBuf slice = buffer.readSlice((int) numBytes); + return new ByteInput(slice, bitsCached, cache, numBits); + } + + /** + * Reads the next byte from the byte stream, ignoring alignment. + */ + 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) { + assert (bits & (Byte.SIZE - 1)) == 0; + return bitInput.incrementBitsRead(bits); + } + + long decrementBitsRead(long bits) { + assert (bits & (Byte.SIZE - 1)) == 0; + return bitInput.decrementBitsRead(bits); + } + + @Override + public short read8u() { + assert aligned() : "not aligned"; + try { + incrementBitsRead(Byte.SIZE); + return buffer.readUnsignedByte(); + } catch (IndexOutOfBoundsException t) { + throw new EndOfInput(t); + } + } + + @Override + public int read16u() { + assert aligned() : "not aligned"; + try { + incrementBitsRead(Short.SIZE); + return buffer.readUnsignedShortLE(); + } catch (IndexOutOfBoundsException t) { + throw new EndOfInput(t); + } + } + + @Override + public long read32u() { + assert aligned() : "not aligned"; + try { + incrementBitsRead(Integer.SIZE); + return buffer.readUnsignedIntLE(); + } catch (IndexOutOfBoundsException t) { + throw new EndOfInput(t); + } + } + + @Override + public byte read8() { + assert aligned() : "not aligned"; + try { + incrementBitsRead(Byte.SIZE); + return buffer.readByte(); + } catch (IndexOutOfBoundsException t) { + throw new EndOfInput(t); + } + } + + @Override + public short read16() { + assert aligned() : "not aligned"; + try { + incrementBitsRead(Short.SIZE); + return buffer.readShortLE(); + } catch (IndexOutOfBoundsException t) { + throw new EndOfInput(t); + } + } + + @Override + public int read32() { + assert aligned() : "not aligned"; + try { + incrementBitsRead(Integer.SIZE); + return buffer.readIntLE(); + } catch (IndexOutOfBoundsException t) { + throw new EndOfInput(t); + } + } + + @Override + public long read64() { + assert aligned() : "not aligned"; + try { + incrementBitsRead(Long.SIZE); + return buffer.readLongLE(); + } catch (IndexOutOfBoundsException t) { + throw new EndOfInput(t); + } + } + + @Override + public byte[] readBytes(int len) { + return readBytes(new byte[len]); + } + + @Override + public byte[] readBytes(byte[] dst) { + return readBytes(dst, 0, dst.length); + } + + @Override + public byte[] readBytes(byte[] dst, int dstOffset, int len) { + assert aligned() : "not aligned"; + try { + incrementBitsRead((long) len * Byte.SIZE); + buffer.readBytes(dst, dstOffset, len); + return dst; + } catch (IndexOutOfBoundsException t) { + throw new EndOfInput(t); + } + } + + @Override + public String readString(int len) { + assert aligned() : "not aligned"; + try { + incrementBitsRead((long) len * Byte.SIZE); + return buffer.readCharSequence(len, CharsetUtil.US_ASCII).toString(); + } catch (IndexOutOfBoundsException t) { + throw new EndOfInput(t); + } + } +} diff --git a/core/src/com/riiablo/io/nio/EndOfInput.java b/core/src/com/riiablo/io/nio/EndOfInput.java new file mode 100644 index 00000000..171a16e4 --- /dev/null +++ b/core/src/com/riiablo/io/nio/EndOfInput.java @@ -0,0 +1,13 @@ +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); + } +} diff --git a/core/src/com/riiablo/io/nio/UnalignedReader.java b/core/src/com/riiablo/io/nio/UnalignedReader.java new file mode 100644 index 00000000..9447b161 --- /dev/null +++ b/core/src/com/riiablo/io/nio/UnalignedReader.java @@ -0,0 +1,88 @@ +package com.riiablo.io.nio; + +interface UnalignedReader extends Aligned, AlignedReader { + int MAX_ULONG_BITS = Long.SIZE - 1; + int MAX_UINT_BITS = Integer.SIZE - 1; + int MAX_USHORT_BITS = Short.SIZE - 1; + int MAX_UBYTE_BITS = Byte.SIZE - 1; + int MAX_UNSIGNED_BITS = MAX_ULONG_BITS; + + int bitsCached(); + long cache(); + + long bitsRead(); + long bitsRemaining(); + long numBits(); + + /** + * Reads up to {@value #MAX_UBYTE_BITS} bits as unsigned and casts the result + * into a {@code byte}. + */ + byte read7u(int bits); + + /** + * Reads up to {@value #MAX_USHORT_BITS} bits as unsigned and casts the + * result into a {@code short}. + */ + short read15u(int bits); + + /** + * Reads up to {@value #MAX_UINT_BITS} bits as unsigned and casts the result + * into a {@code int}. + */ + int read31u(int bits); + + /** + * Reads up to {@value #MAX_ULONG_BITS} bits as unsigned and casts the result + * into a {@code long}. + */ + long read63u(int bits); + + /** + * Reads {@code 1} bit as a {@code boolean}. + * + * @see #read1() + */ + boolean readBoolean(); + + /** + * Reads {@code 1} bit as a {@code byte}. + */ + byte read1(); + + /** + * Reads up to {@value Byte#SIZE} bits as a sign-extended {@code byte}. + */ + byte read8(int bits); + + /** + * Reads up to {@value Short#SIZE} bits as a sign-extended {@code short}. + */ + short read16(int bits); + + /** + * Reads up to {@value Integer#SIZE} bits as a sign-extended {@code int}. + */ + int read32(int bits); + + /** + * Reads up to {@value Long#SIZE} bits as a sign-extended {@code long}. + */ + long read64(int bits); + + /** + * 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). + */ + long readRaw(int bits); + + /** + * Reads n 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) + */ + String readString(int len, int bits, boolean nullTerminated); +}