package org.ripple.power.txns.btc;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List;
/**
* SerializedBuffer handles byte stream serialization and deserialization using
* the little-endian format
*/
public class SerializedBuffer {
/** UTF-8 character set */
private final Charset utf8Charset = Charset.forName("UTF-8");
/** Default buffer size */
private static final int defaultSize = 256;
/** Buffer size increment */
private static final int incrementSize = 512;
/** Byte buffer */
private byte[] bytes;
/** Buffer start */
private int bufferStart;
/** Buffer limit */
private int bufferLimit;
/** Current buffer offset */
private int offset;
/** Segment start offset */
private int segmentOffset;
/**
* Create a new serialized buffer. The buffer start is set to the beginning of the
* byte array and the buffer limit is set to the end of the byte array.
*
* @param buffer Backing byte array
*/
public SerializedBuffer(byte[] buffer) {
this(buffer, 0, buffer.length);
}
/**
* Create a new serialized buffer. The buffer start is set to the byte array offset
* and the buffer limit is set to the buffer start plus the array length.
*
* @param buffer Backing byte array
* @param bufOffset Array offset
* @param bufLength Array length
*/
public SerializedBuffer(byte[] buffer, int bufOffset, int bufLength) {
bytes = buffer;
bufferStart = bufOffset;
bufferLimit = bufOffset+bufLength;
offset = bufferStart;
segmentOffset = bufferStart;
}
/**
* Create a new serialized buffer of the specified size
*
* @param initialSize Initial buffer size
*/
public SerializedBuffer(int initialSize) {
this(new byte[initialSize], 0, initialSize);
}
/**
* Create a new serialized buffer with the default size
*/
public SerializedBuffer() {
this(new byte[defaultSize], 0, defaultSize);
}
/**
* Creates a new serialized buffer from an input ByteBuffer. The buffer start is
* set to 0 and the buffer limit is set to the ByteBuffer limit.
*
* @param byteBuffer Input ByteBuffer
*/
public SerializedBuffer(ByteBuffer byteBuffer) {
bytes = byteBuffer.array();
bufferStart = 0;
bufferLimit = byteBuffer.limit();
offset = bufferStart;
segmentOffset = bufferStart;
}
/**
* Return the number of available bytes (buffer limit - current buffer position)
*
* @return Available byte count
*/
public int available() {
return bufferLimit-offset;
}
/**
* Return the buffer start position relative to the backing byte array
*
* @return Buffer start position
*/
public int getBufferStart() {
return bufferStart;
}
/**
* Return the current buffer position relative to the buffer start position
*
* @return Buffer position
*/
public int getPosition() {
return offset-bufferStart;
}
/**
* Set the current buffer position relative to the buffer start position
*
* @param position Buffer position
*/
public void setPosition(int position) {
offset = Math.min(bufferStart+position, bufferLimit);
}
/**
* Return the current segment start relative to the buffer start position
*
* @return Segment start
*/
public int getSegmentStart() {
return segmentOffset-bufferStart;
}
/**
* Start a new segment at the current buffer position
*/
public void setSegmentStart() {
segmentOffset = offset;
}
/**
* Start a new segment at the specified buffer position relative to the buffer start position
*
* @param position Buffer position
*/
public void setSegmentStart(int position) {
segmentOffset = Math.min(bufferStart+position, bufferLimit);
}
/**
* Skip one or more bytes. If the skip count exceeds the buffer limit, the
* current buffer position will be set to the buffer limit.
*
* @param count Skip count
*/
public void skip(int count) {
offset = Math.min(offset+count, bufferLimit);
}
/**
* Reset the buffer and segment offsets to the buffer start
*/
public void rewind() {
offset = bufferStart;
segmentOffset = bufferStart;
}
/**
* Return the backing array for this buffer
*
* @return Byte array
*/
public byte[] array() {
return bytes;
}
/**
* Return the byte array for the serialized buffer. A new array will be allocated
* if the used portion of the buffer is not the same size as the backing array.
*
* @return Byte array
*/
public byte[] toByteArray() {
return (offset-bufferStart!=bytes.length ? Arrays.copyOfRange(bytes, bufferStart, offset) : bytes);
}
/**
* Return a ByteBuffer for the serialized buffer. The ByteBuffer will use the
* little-endian format.
*
* @return ByteBuffer
*/
public ByteBuffer toByteBuffer() {
return ByteBuffer.wrap(bytes, bufferStart, offset).order(ByteOrder.LITTLE_ENDIAN);
}
/**
* Return a ByteArrayInputStream for the serialized buffer
*
* @return ByteArrayInputStream
*/
public ByteArrayInputStream toInputStream() {
return new ByteArrayInputStream(bytes, bufferStart, offset);
}
/**
* Return a byte value
*
* @return Byte value
* @throws EOFException Byte array underrun
*/
public byte getByte() throws EOFException {
if (offset == bufferLimit)
throw new EOFException("Byte array underrun");
return bytes[offset++];
}
/**
* Return an unsigned byte value
*
* @return Unsigned byte value
* @throws EOFException Byte array underrun
*/
public int getUnsignedByte() throws EOFException {
return (int)getByte()&255;
}
/**
* Store a byte value
*
* @param val Byte value
* @return This buffer
*/
public SerializedBuffer putByte(byte val) {
if (offset == bufferLimit)
reallocateArray(1);
bytes[offset++] = val;
return this;
}
/**
* Store an unsigned byte value
*
* @param val Unsigned byte value
* @return This buffer
*/
public SerializedBuffer putUnsignedByte(int val) {
return putByte((byte)val);
}
/**
* Return a 2-byte unsigned short value
*
* @return Short value
* @throws EOFException Byte array underrun
*/
public int getUnsignedShort() throws EOFException {
if (bufferLimit-offset < 2)
throw new EOFException("Byte array underrun");
return ((int)bytes[offset++]&255) | (((int)bytes[offset++]&255)<<8);
}
/**
* Store a 2-byte unsigned short value
*
* @param val Short value
* @return This buffer
*/
public SerializedBuffer putUnsignedShort(int val) {
if (bufferLimit-offset < 2)
reallocateArray(2);
bytes[offset++] = (byte)val;
bytes[offset++] = (byte)(val>>>8);
return this;
}
/**
* Return a 4-byte integer value
*
* @return Integer value
* @throws EOFException Byte array underrun
*/
public int getInt() throws EOFException {
if (bufferLimit-offset < 4)
throw new EOFException("Byte array underrun");
return ((int)bytes[offset++]&255) |
(((int)bytes[offset++]&255)<<8) |
(((int)bytes[offset++]&255)<<16) |
(((int)bytes[offset++]&255)<<24);
}
/**
* Return a 4-byte unsigned integer value
*
* @return Unsigned integer value
* @throws EOFException Byte array underrun
*/
public long getUnsignedInt() throws EOFException {
return (long)getInt()&0xffffffffL;
}
/**
* Return a variable-length integer value
*
* @return Integer value
* @throws EOFException Byte array underrun
*/
public int getVarInt() throws EOFException {
return (int)decodeVar();
}
/**
* Store a 4-byte integer value
*
* @param val Integer value
* @return This buffer
*/
public SerializedBuffer putInt(int val) {
if (bufferLimit-offset < 4)
reallocateArray(4);
bytes[offset++] = (byte)val;
bytes[offset++] = (byte)(val>>>8);
bytes[offset++] = (byte)(val>>>16);
bytes[offset++] = (byte)(val>>>24);
return this;
}
/**
* Store a 4-byte unsigned integer value
*
* @param val Unsigned integer value
* @return This buffer
*/
public SerializedBuffer putUnsignedInt(long val) {
return putInt((int)val);
}
/**
* Store a variable-length unsigned integer value
*
* @param val Integer value
* @return This buffer
*/
public SerializedBuffer putVarInt(int val) {
return encodeVar((long)val&0x00000000ffffffffL);
}
/**
* Return an 8-byte long value
*
* @return Long value
* @throws EOFException Byte array underrun
*/
public long getLong() throws EOFException {
if (bufferLimit-offset < 8)
throw new EOFException("Byte array underrun");
return ((long)bytes[offset++]&255) |
(((long)bytes[offset++]&255)<<8) |
(((long)bytes[offset++]&255)<<16) |
(((long)bytes[offset++]&255)<<24) |
(((long)bytes[offset++]&255)<<32) |
(((long)bytes[offset++]&255)<<40) |
(((long)bytes[offset++]&255)<<48) |
(((long)bytes[offset++]&255)<<56);
}
/**
* Return a variable-length long value
*
* @return Long value
* @throws EOFException Byte array underrun
*/
public long getVarLong() throws EOFException {
return decodeVar();
}
/**
* Store an 8-byte long value
*
* @param val Long value
* @return This buffer
*/
public SerializedBuffer putLong(long val) {
if (bufferLimit-offset < 8)
reallocateArray(8);
bytes[offset++] = (byte)val;
bytes[offset++] = (byte)(val>>>8);
bytes[offset++] = (byte)(val>>>16);
bytes[offset++] = (byte)(val>>>24);
bytes[offset++] = (byte)(val>>>32);
bytes[offset++] = (byte)(val>>>40);
bytes[offset++] = (byte)(val>>>48);
bytes[offset++] = (byte)(val>>>56);
return this;
}
/**
* Store a variable-length long value
*
* @param val Long value
* @return This buffer
*/
public SerializedBuffer putVarLong(long val) {
return encodeVar(val);
}
/**
* Return a boolean value
*
* @return Boolean value
* @throws EOFException End-of-data processing stream
*/
public boolean getBoolean() throws EOFException {
return (getByte()!=0);
}
/**
* Store a boolean value
*
* @param val Boolean value
* @return This buffer
*/
public SerializedBuffer putBoolean(boolean val) {
return putByte(val ? (byte)1 : (byte)0);
}
/**
* Return a string value. The string length is encoded as a variable-length
* integer followed by the UTF-8 string representation.
*
* @return String value
* @throws EOFException End-of-data processing stream
*/
public String getString() throws EOFException {
int count = getVarInt();
return (count!=0 ? new String(getBytes(count), utf8Charset) : "");
}
/**
* Store a string value. The string length is encoded as a variable-length
* integer followed by the UTF-8 string representation.
*
* @param string String value
* @return This buffer
*/
public SerializedBuffer putString(String string) {
byte[] stringBytes = string.getBytes(utf8Charset);
return putVarInt(stringBytes.length).putBytes(stringBytes);
}
/**
* Return bytes in a new buffer. The byte array length is a variable-length integer preceding
* the bytes.
*
* @return Byte array
* @throws EOFException Byte array underrun
*/
public byte[] getBytes() throws EOFException {
return getBytes(getVarInt());
}
/**
* Return bytes in a new buffer
*
* @param length Number of bytes to read
* @return Byte array
* @throws EOFException Byte array underrun
*/
public byte[] getBytes(int length) throws EOFException {
byte[] buffer;
if (length < 0 || length > bufferLimit-offset)
throw new EOFException("Byte array underrun");
if (length > 0) {
buffer = Arrays.copyOfRange(bytes, offset, offset+length);
offset += length;
} else {
buffer = new byte[0];
}
return buffer;
}
/**
* Return bytes in the supplied array (the array length is the number of bytes to read)
*
* @param buffer Buffer
* @throws EOFException Byte array underrun
*/
public void getBytes(byte[] buffer) throws EOFException {
getBytes(buffer, 0, buffer.length);
}
/**
* Return bytes in the supplied array starting at the specified offset and for the
* specified length
*
* @param buffer Buffer
* @param bufOffset Starting offset in the buffer
* @param length Number of bytes to return
* @throws EOFException Byte array underrun
*/
public void getBytes(byte[] buffer, int bufOffset, int length) throws EOFException {
if (bufferLimit-offset < length)
throw new EOFException("Byte array underrun");
if (length > 0) {
System.arraycopy(bytes, offset, buffer, bufOffset, length);
offset += length;
}
}
/**
* Store a byte array
*
* @param buffer Buffer
* @return This buffer
*/
public SerializedBuffer putBytes(byte[] buffer) {
return putBytes(buffer, 0, buffer.length);
}
/**
* Store a byte array
*
* @param buffer Buffer
* @param bufOffset Starting offset in the buffer
* @param length Number of bytes to store
* @return This buffer
*/
public SerializedBuffer putBytes(byte[] buffer, int bufOffset, int length) {
if (bufferLimit-offset < length)
reallocateArray(length);
if (length > 0) {
System.arraycopy(buffer, bufOffset, bytes, offset, length);
offset += length;
}
return this;
}
/**
* Store a list of byte serializable elements
*
* @param byteList List of byte serializable elements
* @return This buffer
*/
public SerializedBuffer putBytes(List<? extends ByteSerializable> byteList) {
for(ByteSerializable elem:byteList){
elem.getBytes(this);
}
return this;
}
/**
* Return the bytes in the current segment as a new byte array
*
* @return Segment bytes
*/
public byte[] getSegmentBytes() {
return Arrays.copyOfRange(bytes, segmentOffset, offset);
}
/**
* Allocate a new backing array
*
* @param minLength Minimum length
*/
private void reallocateArray(int minLength) {
int increment = Math.max(minLength, incrementSize);
if (bufferLimit-bufferStart == bytes.length) {
bytes = Arrays.copyOf(bytes, bytes.length+increment);
bufferLimit += increment;
} else {
byte[] newBytes = new byte[bufferLimit-bufferStart+increment];
System.arraycopy(bytes, bufferStart, newBytes, 0, bufferLimit-bufferStart);
bytes = newBytes;
offset = offset-bufferStart;
segmentOffset = segmentOffset-bufferStart;
bufferStart = 0;
bufferLimit = bytes.length;
}
}
/**
* Decode a variable-length unsigned numeric value
*
* @return Decoded value
* @throws EOFException Byte array underrun
*/
private long decodeVar() throws EOFException {
if (offset == bufferLimit)
throw new EOFException("Byte array underrun");
long value;
int first = (int)bytes[offset++]&255;
if (first < 253) {
// 8 bits
value = first;
} else if (first == 253) {
// 16 bits
value = getUnsignedShort();
} else if (first == 254) {
// 32 bits
value = getUnsignedInt();
} else {
// 64 bits
value = getLong();
}
return value;
}
/**
* Encode a variable-length unsigned numeric value
*
* @param val Value to be encoded
* @return This buffer
*/
private SerializedBuffer encodeVar(long val) {
if (bufferLimit-offset < 9)
reallocateArray(9);
if ((val&0xFFFFFFFF00000000L) != 0) {
bytes[offset++] = (byte)255;
putLong(val);
} else if ((val&0x00000000FFFF0000L) != 0) {
// 1 marker + 4 data bytes
bytes[offset++] = (byte)254;
putUnsignedInt(val);
} else if (val >= 253L) {
// 1 marker + 2 data bytes
bytes[offset++] = (byte)253;
putUnsignedShort((int)val);
} else {
// Single data byte
bytes[offset++] = (byte)val;
}
return this;
}
}