package org.ripple.power.txns.btc; import java.io.EOFException; import java.io.InputStream; import java.io.IOException; import org.ripple.power.Helper; /** * A VarInt is an unsigned variable-length encoded integer using the bitcoin encoding (called the 'compact size' * in the reference client). It consists of a marker byte and zero or more data bytes as follows: * <pre> * Value Marker Data * ====== ====== ==== * 0-252 0-252 0 bytes * 253 to 2^16-1 253 2 bytes * 2^16 to 2^32-1 254 4 bytes * 2^32 to 2^64-1 255 8 bytes * </pre> */ public final class VarInt { /** The value of this VarInt */ private final long value; /** The encoded size of this VarInt */ private int encodedSize; /** * Creates a new VarInt with the requested value * * @param value Requested value */ public VarInt(long value) { this.value = value; encodedSize = sizeOf(value); } /** * Creates a new VarInt from a byte array in little-endian format * * @param buf Byte array * @param offset Starting offset into the array * @throws EOFException Buffer is too small */ public VarInt(byte[]buf, int offset) throws EOFException { if (offset > buf.length) throw new EOFException("End-of-data while processing VarInt"); int first = 0x00FF&(int)buf[offset]; if (first < 253) { // 8 bits. value = first; encodedSize = 1; } else if (first == 253) { // 16 bits. if (offset+2 > buf.length) throw new EOFException("End-of-data while processing VarInt"); value = (0x00FF&(int)buf[offset+1]) | ((0x00FF&(int)buf[offset+2])<<8); encodedSize = 3; } else if (first == 254) { // 32 bits. if (offset+5 > buf.length) throw new EOFException("End-of-data while processing VarInt"); value = Helper.readUint32LE(buf, offset+1); encodedSize = 5; } else { // 64 bits. if (offset+9 > buf.length) throw new EOFException("End-of-data while processing VarInt"); value = Helper.readUint64LE(buf, offset+1); encodedSize = 9; } } /** * Creates a new VarInt from an input stream encoded in little-endian format * * @param in Input stream * @throws EOFException End-of-data processing stream * @throws IOException I/O error processing stream */ public VarInt(InputStream in) throws EOFException, IOException { int count; int first = in.read(); if (first < 0) throw new EOFException("End-of-data while processing VarInt"); if (first < 253) { // 8 bits. value = first; encodedSize = 1; } else if (first == 253) { // 16 bits. byte[] buf = new byte[2]; count = in.read(buf, 0, 2); if (count < 2) throw new EOFException("End-of-data while processing VarInt"); value = (0x00FF&(int)buf[0]) | ((0x00FF&(int)buf[1])<<8); encodedSize = 3; } else if (first == 254) { // 32 bits. byte[] buf = new byte[4]; count = in.read(buf, 0, 4); if (count < 4) throw new EOFException("End-of-data while processing VarInt"); value = Helper.readUint32LE(buf, 0); encodedSize = 5; } else { // 64 bits. byte[] buf = new byte[8]; count = in.read(buf, 0, 8); if (count < 8) throw new EOFException("End-of-data while processing VarInt"); value = Helper.readUint64LE(buf, 0); encodedSize = 9; } } /** * Returns the value of thie VarInt as an int * * @return Integer value */ public int toInt() { return (int)value; } /** * Returns the value of this VarInt as a long * * @return Long value */ public long toLong() { return value; } /** * Returns the encoded size of this VarInt * * @return Encoded size */ public int getEncodedSize() { return encodedSize; } /** * Returns the encoded VarInt size * * @param bytes Encoded byte stream * @param offset Offset of the encoded VarInt * @return Encoded size */ public static int sizeOf(byte[] bytes, int offset) { int length; int varLength = (int)bytes[offset]&0xff; if (varLength < 253) length = 1; else if (varLength == 253) length = 3; else if (varLength == 254) length = 5; else length = 9; return length; } /** * Returns the encoded size of the given unsigned integer value. * * @param value Value to be encoded * @return Encoded size */ public static int sizeOf(int value) { int minSize; long tValue = ((long)value)&0xffffffffL; if (tValue < 253L) minSize = 1; // Single data byte else if (tValue < 65536L) minSize = 3; // 1 marker + 2 data bytes else minSize = 5; // 1 marker + 4 data bytes return minSize; } /** * Returns the encoded size of the given unsigned long value * * @param value Value to be encoded * @return Encoded size */ public static int sizeOf(long value) { int minSize; if ((value&0xFFFFFFFF00000000L) != 0) { // 1 marker + 8 data bytes minSize = 9; } else if ((value&0x00000000FFFF0000L) != 0) { // 1 marker + 4 data bytes minSize = 5; } else if (value >= 253L) { // 1 marker + 2 data bytes minSize = 3; } else { // Single data byte minSize = 1; } return minSize; } /** * Encode the value in little-endian format * * @return Encoded byte stream */ public byte[] encode() { return encode(value); } /** * Encode the value in little-endian format * * @param value Value to encode * @return Byte array */ public static byte[] encode(long value) { byte[] bytes; if ((value&0xFFFFFFFF00000000L) != 0) { // 1 marker + 8 data bytes bytes = new byte[9]; bytes[0] = (byte)255; Helper.uint64ToByteArrayLE(value, bytes, 1); } else if ((value&0x00000000FFFF0000L) != 0) { // 1 marker + 4 data bytes bytes = new byte[5]; bytes[0] = (byte)254; Helper.uint32ToByteArrayLE(value, bytes, 1); } else if (value >= 253L) { // 1 marker + 2 data bytes bytes = new byte[]{(byte)253, (byte)value, (byte)(value>>8)}; } else { // Single data byte bytes = new byte[]{(byte)value}; } return bytes; } }