package org.ripple.power; import java.math.BigDecimal; import java.math.BigInteger; import java.nio.ByteBuffer; import java.util.List; import org.ripple.power.RippleSchemas.BinaryFormatField; import org.ripple.power.RippleSchemas.PrimitiveTypes; import org.ripple.power.txns.IssuedCurrency; import com.ripple.core.coretypes.Amount; import com.ripple.core.coretypes.STArray; import com.ripple.core.coretypes.STObject; public class RippleSerializer { protected static final long MIN_VALUE = 1000000000000000l; protected static final long MAX_VALUE = 9999999999999999l; public RippleObject readBinaryObject(ByteBuffer input) { RippleObject serializedObject = new RippleObject(); while (input.hasRemaining()) { byte firstByte = input.get(); int type = (0xF0 & firstByte) >> 4; if (type == 0) { type = input.get(); } int field = 0x0F & firstByte; if (field == 0) { field = input.get(); firstByte = (byte) field; } BinaryFormatField serializedField = BinaryFormatField.lookup(type, field); Object value = readPrimitive(input, serializedField.primitive); serializedObject.fields.put(serializedField, value); } return serializedObject; } protected Object readPrimitive(ByteBuffer input, PrimitiveTypes primitive) { if (primitive == PrimitiveTypes.UINT16) { return 0xFFFFFFFF & input.getShort(); } else if (primitive == PrimitiveTypes.UINT32) { return 0xFFFFFFFFFFFFFFFFl & input.getInt(); } else if (primitive == PrimitiveTypes.UINT64) { byte[] eightBytes = new byte[8]; input.get(eightBytes); return new BigInteger(1, eightBytes); } else if (primitive == PrimitiveTypes.HASH128) { byte[] sixteenBytes = new byte[16]; input.get(sixteenBytes); return sixteenBytes; } else if (primitive == PrimitiveTypes.HASH256) { byte[] thirtyTwoBytes = new byte[32]; input.get(thirtyTwoBytes); return thirtyTwoBytes; } else if (primitive == PrimitiveTypes.AMOUNT) { return readAmount(input); } else if (primitive == PrimitiveTypes.VARIABLE_LENGTH) { return readVariableLength(input); } else if (primitive == PrimitiveTypes.ACCOUNT) { return readAccount(input); } else if (primitive == PrimitiveTypes.OBJECT) { throw new RuntimeException("Object type, not yet supported"); } else if (primitive == PrimitiveTypes.ARRAY) { throw new RuntimeException("Array type, not yet supported"); } else if (primitive == PrimitiveTypes.UINT8) { return 0xFFFF & input.get(); } else if (primitive == PrimitiveTypes.HASH160) { return readIssuer(input); } else if (primitive == PrimitiveTypes.PATHSET) { return readPathSet(input); } else if (primitive == PrimitiveTypes.VECTOR256) { throw new RuntimeException("Vector"); } throw new RuntimeException("Unsupported primitive " + primitive); } protected RippleAddress readAccount(ByteBuffer input) { byte[] accountBytes = readVariableLength(input); return new RippleAddress(accountBytes); } protected IssuedCurrency readAmount(ByteBuffer input) { long offsetNativeSignMagnitudeBytes = input.getLong(); boolean isXRPAmount = (0x8000000000000000l & offsetNativeSignMagnitudeBytes) == 0; int sign = (0x4000000000000000l & offsetNativeSignMagnitudeBytes) == 0 ? -1 : 1; int offset = (int) ((offsetNativeSignMagnitudeBytes & 0x3FC0000000000000l) >>> 54); long longMagnitude = offsetNativeSignMagnitudeBytes & 0x3FFFFFFFFFFFFFl; if (isXRPAmount) { BigDecimal magnitude = BigDecimal.valueOf(sign * longMagnitude); return new IssuedCurrency(magnitude); } else { String currencyStr = readCurrency(input); RippleAddress issuer = readIssuer(input); if (offset == 0 || longMagnitude == 0) { return new IssuedCurrency(BigDecimal.ZERO, issuer, currencyStr); } int decimalPosition = 97 - offset; if (decimalPosition < IssuedCurrency.MIN_SCALE || decimalPosition > IssuedCurrency.MAX_SCALE) { throw new RuntimeException("invalid scale " + decimalPosition); } BigInteger biMagnitude = BigInteger.valueOf(sign * longMagnitude); BigDecimal fractionalValue = new BigDecimal(biMagnitude, decimalPosition); return new IssuedCurrency(fractionalValue, issuer, currencyStr); } } protected RippleAddress readIssuer(ByteBuffer input) { byte[] issuerBytes = new byte[20]; input.get(issuerBytes); return new RippleAddress(issuerBytes); } protected String readCurrency(ByteBuffer input) { byte[] unknown = new byte[12]; input.get(unknown); byte[] currency = new byte[8]; input.get(currency); return new String(currency, 0, 3); } protected byte[] readVariableLength(ByteBuffer input) { int byteLen = 0; int firstByte = input.get(); int secondByte = 0; if (firstByte < 192) { byteLen = firstByte; } else if (firstByte < 240) { secondByte = input.get(); byteLen = 193 + (firstByte - 193) * 256 + secondByte; } else if (firstByte < 254) { secondByte = input.get(); int thirdByte = input.get(); byteLen = 12481 + (firstByte - 241) * 65536 + secondByte * 256 + thirdByte; } else { throw new RuntimeException("firstByte=" + firstByte + ", value reserved"); } byte[] variableBytes = new byte[byteLen]; input.get(variableBytes); return variableBytes; } protected RipplePathSet readPathSet(ByteBuffer input) { RipplePathSet pathSet = new RipplePathSet(); RipplePath path = null; while (true) { byte pathElementType = input.get(); if (pathElementType == (byte) 0x00) { break; } if (path == null) { path = new RipplePath(); pathSet.add(path); } if (pathElementType == (byte) 0xFF) { path = null; continue; } RipplePathElement pathElement = new RipplePathElement(); path.add(pathElement); if ((pathElementType & 0x01) != 0) { pathElement.account = readIssuer(input); } if ((pathElementType & 0x10) != 0) { pathElement.currency = readCurrency(input); } if ((pathElementType & 0x20) != 0) { pathElement.issuer = readIssuer(input); } } return pathSet; } public ByteBuffer writeBinaryObject(RippleObject serializedObj) { ByteBuffer output = ByteBuffer.allocate(2000); List<BinaryFormatField> sortedFields = serializedObj.getSortedField(); for (BinaryFormatField field : sortedFields) { byte typeHalfByte = 0; if (field.primitive.typeCode <= 15) { typeHalfByte = (byte) (field.primitive.typeCode << 4); } byte fieldHalfByte = 0; if (field.fieldId <= 15) { fieldHalfByte = (byte) (field.fieldId & 0x0F); } output.put((byte) (typeHalfByte | fieldHalfByte)); if (typeHalfByte == 0) { output.put((byte) field.primitive.typeCode); } if (fieldHalfByte == 0) { output.put((byte) field.fieldId); } writePrimitive(output, field.primitive, serializedObj.getField(field)); } output.flip(); ByteBuffer compactBuffer = ByteBuffer.allocate(output.limit()); compactBuffer.put(output); compactBuffer.flip(); return compactBuffer; } protected void writePrimitive(ByteBuffer output, PrimitiveTypes primitive, Object value) { if (primitive == PrimitiveTypes.UINT16) { int intValue = (int) value; if (intValue > 0xFFFF) { throw new RuntimeException("UINT16 overflow for value " + value); } output.put((byte) (intValue >> 8 & 0xFF)); output.put((byte) (intValue & 0xFF)); } else if (primitive == PrimitiveTypes.UINT32) { long longValue = (long) value; if (longValue > 0xFFFFFFFFl) { throw new RuntimeException("UINT32 overflow for value " + value); } output.put((byte) (longValue >> 24 & 0xFF)); output.put((byte) (longValue >> 16 & 0xFF)); output.put((byte) (longValue >> 8 & 0xFF)); output.put((byte) (longValue & 0xFF)); } else if (primitive == PrimitiveTypes.UINT64) { byte[] biBytes = RipplePrivateKey.bigIntegerToBytes( (BigInteger) value, 8); if (biBytes.length != 8) { throw new RuntimeException("UINT64 overflow for value " + value); } output.put(biBytes); } else if (primitive == PrimitiveTypes.HASH128) { byte[] sixteenBytes = (byte[]) value; if (sixteenBytes.length != 16) { throw new RuntimeException("value " + value + " is not a HASH128"); } output.put(sixteenBytes); } else if (primitive == PrimitiveTypes.HASH256) { byte[] thirtyTwoBytes = (byte[]) value; if (thirtyTwoBytes.length != 32) { throw new RuntimeException("value " + value + " is not a HASH256"); } output.put(thirtyTwoBytes); } else if (primitive == PrimitiveTypes.AMOUNT) { if (value instanceof String) { writeAmount(output, new IssuedCurrency((String) value)); } else { writeAmount(output, (IssuedCurrency) value); } } else if (primitive == PrimitiveTypes.VARIABLE_LENGTH) { if (value instanceof byte[]) { writeVariableLength(output, (byte[]) value); } else if (value instanceof String) { writeVariableLength(output, CoinUtils.fromHex((String) value)); } else { throw new RuntimeException("Variable type, not yet supported"); } } else if (primitive == PrimitiveTypes.ACCOUNT) { if (value instanceof String) { writeAccount(output, new RippleAddress((String) value)); } else { writeAccount(output, (RippleAddress) value); } } else if (primitive == PrimitiveTypes.OBJECT) { if (value instanceof STObject) { output.put(((STObject) value).toBytes()); } else { throw new RuntimeException("Object type, not yet supported"); } } else if (primitive == PrimitiveTypes.ARRAY) { if (value instanceof STArray) { output.put(((STArray) value).toBytes()); } else { throw new RuntimeException("Array type, not yet supported"); } } else if (primitive == PrimitiveTypes.UINT8) { int intValue = (int) value; if (intValue > 0xFF) { throw new RuntimeException("UINT8 overflow for value " + value); } output.put((byte) value); } else if (primitive == PrimitiveTypes.HASH160) { writeIssuer(output, (RippleAddress) value); } else if (primitive == PrimitiveTypes.PATHSET) { writePathSet(output, (RipplePathSet) value); } else if (primitive == PrimitiveTypes.VECTOR256) { throw new RuntimeException("Vector"); } else { throw new RuntimeException("Unsupported primitive " + primitive); } } protected void writePathSet(ByteBuffer output, RipplePathSet pathSet) { loopPathSet: for (int i = 0; i < pathSet.size(); i++) { RipplePath path = pathSet.get(i); for (int j = 0; j < path.size(); j++) { RipplePathElement pathElement = path.get(j); byte pathElementType = 0; if (pathElement.account != null) { pathElementType |= 0x01; } if (pathElement.currency != null) { pathElementType |= 0x10; } if (pathElement.issuer != null) { pathElementType |= 0x20; } output.put(pathElementType); if (pathElement.account != null) { writeIssuer(output, pathElement.account); } if (pathElement.currency != null) { writeCurrency(output, pathElement.currency); } if (pathElement.issuer != null) { writeIssuer(output, pathElement.issuer); } if (i + 1 == pathSet.size() && j + 1 == path.size()) { break loopPathSet; } } output.put((byte) 0xFF); } output.put((byte) 0); } protected void writeIssuer(ByteBuffer output, RippleAddress value) { byte[] issuerBytes = value.getBytes(); output.put(issuerBytes); } protected void writeAccount(ByteBuffer output, RippleAddress address) { writeVariableLength(output, address.getBytes()); } protected void writeVariableLength(ByteBuffer output, byte[] value) { if (value.length < 192) { output.put((byte) value.length); } else if (value.length < 12480) { int firstByte = (value.length / 256) + 193; output.put((byte) firstByte); int secondByte = value.length - firstByte - 193; output.put((byte) secondByte); } else if (value.length < 918744) { int firstByte = (value.length / 65536) + 241; output.put((byte) firstByte); int secondByte = (value.length - firstByte) / 256; output.put((byte) secondByte); int thirdByte = value.length - firstByte - secondByte - 12481; output.put((byte) thirdByte); } output.put(value); } protected void writeAmount(ByteBuffer output, IssuedCurrency denominatedCurrency) { long offsetNativeSignMagnitudeBytes = 0; if (denominatedCurrency.amount.signum() > 0) { offsetNativeSignMagnitudeBytes |= 0x4000000000000000l; } if (denominatedCurrency.currency == null) { long drops = denominatedCurrency.amount.longValue(); offsetNativeSignMagnitudeBytes |= drops; output.putLong(offsetNativeSignMagnitudeBytes); } else { Amount amount = Amount .fromIOUString(denominatedCurrency.toString()); output.put(amount.toBytes()); } } protected void writeCurrency(ByteBuffer output, String currency) { byte[] currencyBytes = new byte[20]; System.arraycopy(currency.getBytes(), 0, currencyBytes, 12, 3); output.put(currencyBytes); } }