Bitcoin VarInt data type decoding steps with code examples!

Most fields of the Bitcoin data structures have a fixed size of Byte. That is an easy way, but each field must reserve the maximum count of Byte, even when you just need the least possible count. Bitcoin defines a very simple to understand encoding for numbers, to save Byte on numbers that could be very big but are likely small. This data type is called a VarInt. The perfect encoding doesn’t exist, it is just possible to work with probability. The result basing strict on the real data which are usual only partial predictable. To decode a VarInt, follow this steps:

Decode Bitcoin VarInt:

1. If the first Byte is not 0xFD, 0xFE or 0xFF, respectively (decimal unsigned 253, 254 or 255), the VarInt is just this single Byte.
2. If the first Byte is 0xFD, the following two Byte are the VarInt in Little Endian order, so the first Byte is the lower one.
3. If the first Byte is 0xFE, read the following four Byte in Little Endian.
4. And the same for 0xFF and eight Byte.

Java Bitcoin VarInt example:

The example below implements reading and writing a VarInt as utility methods. The writing progress takes a long data type but always writes the lowest need Byte count, even when use negative numbers. This reading methods on the other side read the VarInt and return them as a sign-extended long. Hence, the expected return value of the Byte 0xFC is 0xFFFF_FFFF_FFFF_FFFC, not 0x0000_0000_0000_00FC in Big Endian order.

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public final class BitcoinUtils {
private BitcoinUtils() {
throw new UnsupportedOperationException("Should never reaches");
}

/**
* @param in The Stream to read the VarInt from
* @return The VarInt sign-extended. So if the value 0xB00B, the returned
* long is 0xFFFF_FFFF_FFFF_B00B
* @throws IOException If the stream throws an error
*/
public static long readVarInt(final InputStream in) throws IOException {

if((b & 0xFFFF_FF00) != 0) {
throw new EOFException("Unexcepted EOF");
}

// Sign-extend the return value by casting
switch(b) {
case(0xFD):
// Correct FD to FF to Byte size
switch(c) {
case(0xFD):
case(0xFE):
case(0xFF):
return((long)((byte)c));
}
return(((long)c));
case(0xFE):
case(0xFF):
}
return((long)((byte)b));
}
public static short readShortLittleEndian(final InputStream in) throws IOException {
}
public static int readIntLittleEndian(final InputStream in) throws IOException {
}
public static long readLongLittleEndian(final InputStream in) throws IOException {
}
public static long readMultibyteNumberLittleEndian(final InputStream in, final int len)
throws IOException {
if(len <= 0) {
throw new IllegalArgumentException("len <= 0");
}

long l = 0;
for(long i = 0, j = in.read(); i < len; i++, j = in.read()) {
if((j & 0xFFFF_FF00) != 0) {
// return(-1);
throw new IllegalArgumentException("Unexpected EOF");
}
l|= (j << (i << 3));
}
return(l);
}

/**
* @param out The Stream to write the VarInt too.
* @param l The long to write. It writes exactly as much bytes as need,
* even for negativ numbers.
* @throws IOException
*/
public static void writeVarInt(final OutputStream out,
final long l) throws IOException {
// If the number is negative and a part just the sign-extends, reset
// all sign-extending bytes to write it like an unsigned of the smaller
// type.
return;
}
}

if((l & 0x8000_0000_0000_0000L) == 0) { // If positive...
if(l <= 0xFC) {
out.write((byte)l);
return;
} else if(l <= 0xFFFF) {
out.write(0xFD);

out.write((byte)l);
out.write((byte)(l >> 8));
return;
} else if(l <= 0x0000_0000_FFFF_FFFFL) {
out.write(0xFE);

out.write((byte)l);
out.write((byte)(l >> 8));
out.write((byte)(l >> 16));
out.write((byte)(l >> 24));
return;
}
}

out.write(0xFF);
out.write((byte)l);
out.write((byte)(l >> 8));
out.write((byte)(l >> 16));
out.write((byte)(l >> 24));

out.write((byte)(l >> 32));
out.write((byte)(l >> 40));
out.write((byte)(l >> 48));
out.write((byte)(l >> 56));
}
public static void writeShortLittleEndian(final OutputStream out,
final short s) throws IOException {
writeMultibyteNumberLittleEndian(out, 2, s);
}
public static void writeIntLittleEndian(final OutputStream out,
final int i) throws IOException {
writeMultibyteNumberLittleEndian(out, 4, i);
}
public static void writeLongLittleEndian(final OutputStream out,
final long l) throws IOException {
writeMultibyteNumberLittleEndian(out, 8, l);
}
public static void writeMultibyteNumberLittleEndian(final OutputStream out,
final int size,
final long i) throws IOException {
if(out == null) {
throw new IllegalArgumentException("out == null");
} else if(size <= 0) {
throw new IllegalArgumentException("len <= 0");
}

for(int j = 0; j < size; j++) {
out.write(((byte)(i >> (j << 3))));
}
}
}