Last active
October 15, 2017 13:59
-
-
Save king1600/ef8ea324c6f253c0718bbe3e5b548106 to your computer and use it in GitHub Desktop.
Java 8 port of /discordapp/erlpack/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.protto.erlpack; | |
import java.nio.charset.Charset; | |
import java.util.Arrays; | |
public class Buffer { | |
private byte[] buf; | |
private int length; | |
private int alloc_size; | |
public Buffer(final long size) { | |
length = 0; | |
alloc_size = (int)size; | |
buf = new byte[alloc_size]; | |
} | |
public final int size() { | |
return length; | |
} | |
public byte[] buffer() { | |
return buf; | |
} | |
public void append(final byte[] bytes) { | |
append(bytes, 0, bytes.length); | |
} | |
public void append(final byte[] bytes, final int size) { | |
append(bytes, 0, size); | |
} | |
public void append(final byte[] bytes, final int start, final int size) { | |
if (length + size > alloc_size) { | |
// Grow buffer 2x to avoid excessive re-allocations. | |
alloc_size = (length + size) * 2; | |
buf = Arrays.copyOf(buf, alloc_size); | |
} | |
System.arraycopy(bytes, start, buf, length, size); | |
length += size; | |
} | |
public void appendVersion() { | |
append(new byte[]{ Erlpack.FORMAT_VERSION }); | |
} | |
public void appendNull() { | |
append(new byte[]{Erlpack.SMALL_ATOM_EXT, 3, 'n', 'i', 'l'}); | |
} | |
public void appendTrue() { | |
append(new byte[]{Erlpack.SMALL_ATOM_EXT, 4, 't', 'r', 'u', 'e'}); | |
} | |
public void appendFalse() { | |
append(new byte[]{Erlpack.SMALL_ATOM_EXT, 5, 'f', 'a', 'l', 's', 'e'}); | |
} | |
public void appendByte(final byte i) { | |
append(new byte[]{Erlpack.SMALL_INTEGER_EXT, i}); | |
} | |
public void appendInt(final int i) { | |
append(new byte[]{Erlpack.INTEGER_EXT, | |
(byte)((i >> 24) & 0xFF), | |
(byte)((i >> 16) & 0xFF), | |
(byte)((i >> 8) & 0xFF), | |
(byte)(i & 0xFF) | |
}); | |
} | |
public void appendLong(final long i) { | |
byte[] b = new byte[1 + 2 + Long.BYTES]; | |
b[0] = Erlpack.SMALL_BIG_EXT; | |
b[2] = (byte)(i < 0 ? 1 : 0); | |
long ulong = i < 0 ? -i : Erlpack.toULong(i); | |
byte bytes_enc = 0; | |
while (ulong > 0) { | |
b[3 + (bytes_enc++)] = (byte)(ulong & 0xFF); | |
ulong >>= 8; | |
} | |
b[1] = bytes_enc; | |
append(b, 1 + 2 + bytes_enc); | |
} | |
public void appendDouble(double i) { | |
byte[] b = new byte[1 + Long.BYTES]; | |
b[0] = Erlpack.NEW_FLOAT_EXT; | |
final long d = Double.doubleToLongBits(i); | |
System.arraycopy(Erlpack.toBytes(d, Double.BYTES), 0, b, 1, Double.BYTES); | |
append(b); | |
} | |
public void appendAtom(final String str) { | |
final byte[] bytes = str.getBytes(Charset.defaultCharset()); | |
if (bytes.length < 0xFF - 1) | |
append(new byte[]{Erlpack.SMALL_ATOM_EXT, (byte)bytes.length}); | |
else | |
append(new byte[]{Erlpack.ATOM_EXT, (byte)((bytes.length >> 8) & 0xFF), (byte)(bytes.length & 0xFF)}); | |
append(bytes, 0, bytes.length); | |
} | |
public void appendBinary(final byte[] bytes, final int size) { | |
appendBinary(bytes, 0, size); | |
} | |
public void appendBinary(final byte[] bytes, final int start, final int size) { | |
append(new byte[] { | |
Erlpack.BINARY_EXT, | |
(byte)((size >> 24) & 0xFF), | |
(byte)((size >> 16) & 0xFF), | |
(byte)((size >> 8) & 0xFF), | |
(byte)(size & 0xFF) | |
}); | |
append(bytes, start, size); | |
} | |
public void appendString(final String str) { | |
appendString(str, Charset.defaultCharset()); | |
} | |
public void appendString(final String str, final Charset charset) { | |
final byte[] bytes = str.getBytes(charset); | |
append(new byte[]{Erlpack.STRING_EXT, (byte)((bytes.length >> 8) & 0xFF), (byte)(bytes.length & 0xFF)}); | |
append(bytes); | |
} | |
public void appendTupleHeader(final long size) { | |
if (size < 0xFF) | |
append(new byte[]{Erlpack.SMALL_TUPLE_EXT, (byte)size}); | |
else | |
append(new byte[]{Erlpack.LARGE_TUPLE_EXT, | |
(byte)((size >> 24) & 0xFF), | |
(byte)((size >> 16) & 0xFF), | |
(byte)((size >> 8) & 0xFF), | |
(byte)(size & 0xFF) | |
}); | |
} | |
public void appendNulExt() { | |
append(new byte[]{Erlpack.NIL_EXT}); | |
} | |
public void appendListHeader(final long size) { | |
append(new byte[]{Erlpack.LIST_EXT, | |
(byte)((size >> 24) & 0xFF), | |
(byte)((size >> 16) & 0xFF), | |
(byte)((size >> 8) & 0xFF), | |
(byte)(size & 0xFF) | |
}); | |
} | |
public void appendMapHeader(final long size) { | |
append(new byte[]{Erlpack.MAP_EXT, | |
(byte)((size >> 24) & 0xFF), | |
(byte)((size >> 16) & 0xFF), | |
(byte)((size >> 8) & 0xFF), | |
(byte)(size & 0xFF) | |
}); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.protto.erlpack; | |
import java.util.Map; | |
import java.util.HashMap; | |
import java.util.zip.Inflater; | |
import java.lang.reflect.Field; | |
import java.nio.charset.Charset; | |
public class Decoder { | |
private int offset; | |
private final int size; | |
private final byte[] data; | |
public Decoder(final byte[] stream) throws Exception { | |
this(stream, 0, stream.length, false); | |
} | |
public Decoder(final byte[] stream, final int length) throws Exception { | |
this(stream, 0, length, false); | |
} | |
public Decoder(final byte[] stream, final int offset, final int length) throws Exception { | |
this(stream, offset, length, false); | |
} | |
public Decoder(final byte[] stream, final int offset, final int length, final boolean skipVersion) throws Exception { | |
data = stream; | |
size = length; | |
this.offset = offset; | |
if (!skipVersion) | |
if (read8() != Erlpack.FORMAT_VERSION) | |
throw new Exception("Bad version number"); | |
} | |
private static void overflow(final int bytesOver) throws Exception { | |
throw new Exception("Reading " + bytesOver + " bytes passes the end of the buffer."); | |
} | |
byte read8() throws Exception { | |
if (offset + Byte.BYTES > size) | |
overflow(1); | |
return data[offset++]; | |
} | |
short read16() throws Exception { | |
if (offset + Short.BYTES > size) | |
overflow(2); | |
short value = (short)Erlpack.fromBytes(data, offset, Short.BYTES); | |
offset += Short.BYTES; | |
return value; | |
} | |
int read32() throws Exception { | |
if (offset + Integer.BYTES > size) | |
overflow(2); | |
int value = (int)Erlpack.fromBytes(data, offset, Integer.BYTES); | |
offset += Integer.BYTES; | |
return value; | |
} | |
long read64() throws Exception { | |
if (offset + Long.BYTES > size) | |
overflow(2); | |
long value = (long)Erlpack.fromBytes(data, offset, Long.BYTES); | |
offset += Long.BYTES; | |
return value; | |
} | |
Byte decodeByte() throws Exception { | |
return read8(); | |
} | |
Integer decodeInteger() throws Exception { | |
return read32(); | |
} | |
Object[] decodeArray(final int size) throws Exception { | |
Object[] array = new Object[size]; | |
for (int i = 0; i < size; i++) | |
array[i] = unpack(); | |
return array; | |
} | |
Object[] decodeList() throws Exception { | |
final int length = read32(); | |
Object[] array = decodeArray(length); | |
final byte tailMarker = read8(); | |
if (tailMarker != Erlpack.NIL_EXT) | |
throw new Exception("List doesn't end with a tail marker, but it must!"); | |
return array; | |
} | |
Object[] decodeTuple(final int length) throws Exception { | |
return decodeArray(length); | |
} | |
Object[] decodeNil() throws Exception { | |
return new Object[]{}; | |
} | |
Map<String, Object> decodeMap() throws Exception { | |
final int length = read32(); | |
Map<String, Object> map = new HashMap<>(); | |
for (int i = 0; i < length; i++) | |
map.put(unpack().toString(), unpack()); | |
return map; | |
} | |
String readString(final int length) throws Exception { | |
if (offset + length > size) | |
throw new Exception("Reading sequence past the end of the buffer"); | |
String result = new String(data, offset, length); | |
offset += length; | |
return result; | |
} | |
Object processAtom(final String atom, final int length) throws Exception { | |
if (atom == null) | |
return null; | |
if (length >= 3 && length <= 5) { | |
if (length == 3 && atom.equals("nil")) | |
return null; | |
else if (length == 4 && atom.equals("null")) | |
return null; | |
else if (length == 4 && atom.equals("true")) | |
return true; | |
else if (length == 5 && atom.equals("false")) | |
return false; | |
} | |
return atom; | |
} | |
Object decodeAtom() throws Exception { | |
final int length = read16(); | |
final String atom = readString(length); | |
return processAtom(atom, length); | |
} | |
Object decodeSmallAtom() throws Exception { | |
final int length = read8(); | |
final String atom = readString(length); | |
return processAtom(atom, length); | |
} | |
Float decodeFloat() throws Exception { | |
final int FLOAT_LENGTH = 31; | |
final String floatStr = readString(FLOAT_LENGTH); | |
if (floatStr == null) | |
return null; | |
return Float.parseFloat(floatStr); | |
} | |
Double decodeNewFloat() throws Exception { | |
return Double.longBitsToDouble(read64()); | |
} | |
long decodeBig(int bytes) throws Exception { | |
final byte sign = read8(); | |
if (bytes > 8) | |
throw new Exception("Unable to decode big ints larger than 8 bytes"); | |
long value = 0; | |
for (int i = 0; i < bytes; i++) { | |
value <<= 8; | |
value |= (data[offset + (bytes - i - 1)] & 0xFF); | |
} | |
offset += bytes; | |
if (bytes <= 4) { | |
if (sign == 0) | |
return value; | |
final boolean signBitsAvailable = (value & (1 << 31)) == 0; | |
if (signBitsAvailable) | |
return -value; | |
} | |
return value; | |
} | |
Long decodeSmallBig() throws Exception { | |
return decodeBig(read8()); | |
} | |
Long decodeLargeBig() throws Exception { | |
return decodeBig(read32()); | |
} | |
byte[] decodeBinaryAsString() throws Exception { | |
final int length = read32(); | |
if (offset + length > size) | |
throw new Exception("Reading sequence past the end of the buffer"); | |
byte[] value = new byte[length]; | |
System.arraycopy(data, offset, value, 0, length); | |
offset += length; | |
return value; | |
} | |
String decodeStringAsList() throws Exception { | |
final int length = read16(); | |
if (offset + length > size) | |
throw new Exception("Reading sequence past the end of the buffer."); | |
byte[] array = new byte[length]; | |
for (int i = 0; i < length; i++) | |
array[i] = decodeByte(); | |
return new String(array, Charset.defaultCharset()); | |
} | |
Object[] decodeSmallTuple() throws Exception { | |
return decodeTuple(read8()); | |
} | |
Object[] decodeLargeTuple() throws Exception { | |
return decodeTuple(read32()); | |
} | |
Object decodeCompressed() throws Exception { | |
final int uncompressedSize = read32(); | |
byte[] output = new byte[uncompressedSize]; | |
Inflater decompresser = new Inflater(); | |
decompresser.setInput(data, offset, size - offset); | |
final int sourceSize = decompresser.inflate(output); | |
decompresser.end(); | |
Decoder children = new Decoder(output, uncompressedSize); | |
return children.unpack(); | |
} | |
Map<String, Object> decodeReference() throws Exception { | |
Map<String, Object> ref = new HashMap<>(); | |
ref.put("node", unpack()); | |
ref.put("id", new int[] { read32() }); | |
ref.put("creation", read8()); | |
return ref; | |
} | |
Map<String, Object> decodeNewReference() throws Exception { | |
Map<String, Object> ref = new HashMap<>(); | |
final int len = read16(); | |
ref.put("node", unpack()); | |
ref.put("creation", read8()); | |
int[] ids = new int[len]; | |
for (int i = 0; i < len; i++) | |
ids[i] = read32(); | |
ref.put("id", ids); | |
return ref; | |
} | |
Map<String, Object> decodePort() throws Exception { | |
Map<String, Object> port = new HashMap<>(); | |
port.put("node", unpack()); | |
port.put("id", read32()); | |
port.put("creation", read8()); | |
return port; | |
} | |
Map<String, Object> decodePID() throws Exception { | |
Map<String, Object> pid = new HashMap<>(); | |
pid.put("node", unpack()); | |
pid.put("id", read32()); | |
pid.put("serial", read32()); | |
pid.put("creation", read8()); | |
return pid; | |
} | |
Map<String, Object> decodeExport() throws Exception { | |
Map<String, Object> export = new HashMap<>(); | |
export.put("mod", unpack()); | |
export.put("fun", unpack()); | |
export.put("arity", unpack()); | |
return export; | |
} | |
public Object unpack() throws Exception { | |
if (offset >= size) | |
throw new Exception("Unpacking beyond the end of the buffer"); | |
final byte type = read8(); | |
switch (type) { | |
case Erlpack.SMALL_INTEGER_EXT: | |
return decodeByte(); | |
case Erlpack.INTEGER_EXT: | |
return decodeInteger(); | |
case Erlpack.FLOAT_EXT: | |
return decodeFloat(); | |
case Erlpack.NEW_FLOAT_EXT: | |
return decodeNewFloat(); | |
case Erlpack.ATOM_EXT: | |
return decodeAtom(); | |
case Erlpack.SMALL_ATOM_EXT: | |
return decodeSmallAtom(); | |
case Erlpack.SMALL_TUPLE_EXT: | |
return decodeSmallTuple(); | |
case Erlpack.LARGE_TUPLE_EXT: | |
return decodeLargeTuple(); | |
case Erlpack.NIL_EXT: | |
return decodeNil(); | |
case Erlpack.STRING_EXT: | |
return decodeStringAsList(); | |
case Erlpack.LIST_EXT: | |
return decodeList(); | |
case Erlpack.MAP_EXT: | |
return decodeMap(); | |
case Erlpack.BINARY_EXT: | |
return decodeBinaryAsString(); | |
case Erlpack.SMALL_BIG_EXT: | |
return decodeSmallBig(); | |
case Erlpack.LARGE_BIG_EXT: | |
return decodeLargeBig(); | |
case Erlpack.REFERENCE_EXT: | |
return decodeReference(); | |
case Erlpack.NEW_REFERENCE_EXT: | |
return decodeNewReference(); | |
case Erlpack.PORT_EXT: | |
return decodePort(); | |
case Erlpack.PID_EXT: | |
return decodePID(); | |
case Erlpack.EXPORT_EXT: | |
return decodeExport(); | |
case Erlpack.COMPRESSED: | |
return decodeCompressed(); | |
default: | |
throw new Exception("Unsupported erlang term type identifier found"); | |
} | |
} | |
public static<T> T toObject(final Map<String, Object> map, T instance) throws Exception { | |
for (Map.Entry<String, Object> entry : map.entrySet()) | |
setAttribute(instance, entry.getKey(), entry.getValue()); | |
return instance; | |
} | |
public static<T> void setAttribute(final T instance, final String key, final Object value) throws Exception { | |
Field field; | |
Class<?> klass = instance.getClass(); | |
while (klass != null) { | |
try { | |
field = klass.getDeclaredField(key); | |
field.setAccessible(true); | |
if (value instanceof Map) | |
field.set(instance, toObject((Map)value, field.get(instance))); | |
else | |
field.set(instance, value); | |
return; | |
} catch (NoSuchFieldException ex) { | |
klass = klass.getSuperclass(); | |
} catch (Exception ex) { | |
throw new IllegalStateException(ex); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.protto.erlpack; | |
import java.util.Map; | |
import java.util.List; | |
import java.util.Arrays; | |
import java.util.ArrayList; | |
import java.lang.reflect.Field; | |
public class Encoder { | |
private static final long DEFAULT_RECURSE_LIMIT = 0xFF; | |
private static final long INITIAL_BUFFER_SIZE = 1024 * 1024; | |
private Buffer buf; | |
public Encoder() { | |
buf = new Buffer(INITIAL_BUFFER_SIZE); | |
buf.appendVersion(); | |
} | |
public byte[] compile() { | |
byte[] output = Arrays.copyOf(buf.buffer(), buf.size()); | |
buf = null; | |
return output; | |
} | |
public <T> Encoder pack(final T value) throws Exception { | |
return pack(value, DEFAULT_RECURSE_LIMIT); | |
} | |
public <T> Encoder pack(final T value, final long nestLimit) throws Exception { | |
if (nestLimit < 0) | |
throw new Exception("Reached recursion limit"); | |
if (value == null) { | |
buf.appendNull(); | |
} else if (value instanceof Long) { | |
buf.appendLong((Long)value); | |
} else if (value instanceof Integer || value instanceof Byte) { | |
int num = (Integer)value; | |
if (num >= 0 && num <= 255) | |
buf.appendByte((byte)(num & 0xFF)); | |
else | |
buf.appendInt(num); | |
} else if (value instanceof Double) { | |
buf.appendDouble((Double)value); | |
} else if (value instanceof Boolean) { | |
if ((Boolean)value) | |
buf.appendTrue(); | |
else | |
buf.appendFalse(); | |
} else if (value instanceof String) { | |
buf.appendString((String) value); | |
} else if (value instanceof byte[]) { | |
byte[] data = (byte[])value; | |
buf.appendBinary(data, data.length); | |
} else if (value.getClass().isArray()) { | |
final T[] items = (T[]) value; | |
final int length = items.length; | |
if (length == 0) { | |
buf.appendNulExt(); | |
} else { | |
if (length > Integer.MAX_VALUE - 1) | |
throw new Exception("List is too large"); | |
buf.appendListHeader(length); | |
for (int i = 0; i < length; i++) | |
pack(items[i], nestLimit - 1); | |
buf.appendNulExt(); | |
} | |
} else if (value instanceof Map) { | |
Map<?, ?> map = (Map)value; | |
long length = map.entrySet().size(); | |
if (length > Integer.MAX_VALUE - 1) | |
throw new Exception("Object has too many properties"); | |
buf.appendMapHeader(length); | |
for (Map.Entry<?, ?> property : map.entrySet()) { | |
pack(property.getKey(), nestLimit - 1); | |
pack(property.getValue(), nestLimit - 1); | |
} | |
} else { | |
final Field[] properties = getAttributes(value); | |
final int length = properties.length; | |
if (length > Integer.MAX_VALUE - 1) | |
throw new Exception("Object has too many properties"); | |
buf.appendMapHeader(length); | |
for (int i = 0; i < length; i++) { | |
properties[i].setAccessible(true); | |
pack(properties[i].getName(), nestLimit - 1); // key | |
pack(properties[i].get(value), nestLimit - 1); // value | |
} | |
} | |
return this; | |
} | |
private static <T> Field[] getAttributes(final T instance) { | |
final List<Field> attributes = new ArrayList<>(); | |
getAttrs(attributes, instance.getClass()); | |
return attributes.toArray(new Field[]{}); | |
} | |
private static <T> void getAttrs(final List<Field> fields, final Class<?> klass) { | |
if (klass == null) | |
return; | |
for (Field field : klass.getDeclaredFields()) | |
fields.add(field); | |
getAttrs(fields, klass.getSuperclass()); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.protto.erlpack; | |
import java.math.BigInteger; | |
public abstract class Erlpack { | |
private static final BigInteger u64 = BigInteger.ONE.shiftLeft(64); | |
protected static long toULong(final long l) { | |
BigInteger b = BigInteger.valueOf(l); | |
if (b.signum() < 0) | |
b = b.add(u64); | |
return b.longValue(); | |
} | |
protected static byte[] toBytes(long l, final int bytes) { | |
byte[] b = new byte[bytes]; | |
for (int i = bytes - 1; i >= 0; i--) { | |
b[i] = (byte)(l & 0xFF); | |
l >>= 8; | |
} | |
return b; | |
} | |
protected static long fromBytes(final byte[] data, int offset, final int bytes) { | |
long b = 0; | |
for (int i = 0; i < bytes; i++) { | |
b <<= 8; | |
b |= (data[offset + i] & 0xFF); | |
} | |
return b; | |
} | |
protected static final byte | |
FORMAT_VERSION = (byte)131, | |
NEW_FLOAT_EXT = 'F', // 70 [Float64:IEEE float] | |
BIT_BINARY_EXT = 'M', // 77 [UInt32:Len, UInt8:Bits, Len:Data] | |
SMALL_INTEGER_EXT = 'a', // 97 [UInt8:Int] | |
INTEGER_EXT = 'b', // 98 [Int32:Int] | |
FLOAT_EXT = 'c', // 99 [31:Float String] Float in string format (formatted "%.20e", sscanf "%lf"). Superseded by NEW_FLOAT_EXT | |
ATOM_EXT = 'd', // 100 [UInt16:Len, Len:AtomName] max Len is 255 | |
REFERENCE_EXT = 'e', // 101 [atom:Node, UInt32:ID, UInt8:Creation] | |
PORT_EXT = 'f', // 102 [atom:Node, UInt32:ID, UInt8:Creation] | |
PID_EXT = 'g', // 103 [atom:Node, UInt32:ID, UInt32:Serial, UInt8:Creation] | |
SMALL_TUPLE_EXT = 'h', // 104 [UInt8:Arity, N:Elements] | |
LARGE_TUPLE_EXT = 'i', // 105 [UInt32:Arity, N:Elements] | |
NIL_EXT = 'j', // 106 empty list | |
STRING_EXT = 'k', // 107 [UInt16:Len, Len:Characters] | |
LIST_EXT = 'l', // 108 [UInt32:Len, Elements, Tail] | |
BINARY_EXT = 'm', // 109 [UInt32:Len, Len:Data] | |
SMALL_BIG_EXT = 'n', // 110 [UInt8:n, UInt8:Sign, n:nums] | |
LARGE_BIG_EXT = 'o', // 111 [UInt32:n, UInt8:Sign, n:nums] | |
NEW_FUN_EXT = 'p', // 112 [UInt32:Size, UInt8:Arity, 16*Uint6-MD5:Uniq, UInt32:Index, UInt32:NumFree, atom:Module, int:OldIndex, int:OldUniq, pid:Pid, NunFree*ext:FreeVars] | |
EXPORT_EXT = 'q', // 113 [atom:Module, atom:Function, smallint:Arity] | |
NEW_REFERENCE_EXT = 'r', // 114 [UInt16:Len, atom:Node, UInt8:Creation, Len*UInt32:ID] | |
SMALL_ATOM_EXT = 's', // 115 [UInt8:Len, Len:AtomName] | |
MAP_EXT = 't', // 116 [UInt32:Airty, N:Pairs] | |
FUN_EXT = 'u', // 117 [UInt4:NumFree, pid:Pid, atom:Module, int:Index, int:Uniq, NumFree*ext:FreeVars] | |
COMPRESSED = 'P'; // 80 [UInt4:UncompressedSize, N:ZlibCompressedData] | |
public static<T> byte[] pack(final T value) throws Exception { | |
return new Encoder().pack(value).compile(); | |
} | |
public static<T> T unpack(final byte[] stream) throws Exception { | |
return unpack(stream, 0, stream.length); | |
} | |
public static<T> T unpack(final byte[] stream, final int length) throws Exception { | |
return unpack(stream, 0, length); | |
} | |
public static<T> T unpack(final byte[] stream, final int offset, final int length) throws Exception { | |
return (T)(new Decoder(stream, offset, length, false).unpack()); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment