Skip to content

Instantly share code, notes, and snippets.

@king1600
Last active October 15, 2017 13:59
Show Gist options
  • Save king1600/ef8ea324c6f253c0718bbe3e5b548106 to your computer and use it in GitHub Desktop.
Save king1600/ef8ea324c6f253c0718bbe3e5b548106 to your computer and use it in GitHub Desktop.
Java 8 port of /discordapp/erlpack/
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)
});
}
}
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);
}
}
}
}
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());
}
}
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