package com.asteria.net.message;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import com.asteria.net.ByteOrder;
import com.asteria.net.ValueType;
/**
* The {@link Message} implementation that functions as a dynamic buffer wrapper
* backed by a {@link ByteBuf} that is used for reading and writing data.
*
* @author lare96 <http://github.com/lare96>
* @author blakeman8192
*/
public final class MessageBuilder implements Message {
/**
* An array of the bit masks used for writing bits.
*/
private static final int[] BIT_MASK = { 0, 0x1, 0x3, 0x7, 0xf, 0x1f, 0x3f, 0x7f, 0xff, 0x1ff, 0x3ff, 0x7ff, 0xfff, 0x1fff, 0x3fff,
0x7fff, 0xffff, 0x1ffff, 0x3ffff, 0x7ffff, 0xfffff, 0x1fffff, 0x3fffff, 0x7fffff, 0xffffff, 0x1ffffff, 0x3ffffff, 0x7ffffff,
0xfffffff, 0x1fffffff, 0x3fffffff, 0x7fffffff, -1 };
/**
* The default capacity of this buffer.
*/
private static final int DEFAULT_CAP = 128;
/**
* The backing byte buffer used to read and write data.
*/
private ByteBuf buf;
/**
* The position of the buffer when a variable length message is created.
*/
private int varLengthIndex = 0;
/**
* The current bit position when writing bits.
*/
private int bitIndex = 0;
/**
* Creates a new {@link MessageBuilder} with the {@code buf} backing buffer.
*
* @param buf
* the backing buffer used to read and write data.
*/
private MessageBuilder(ByteBuf buf) {
this.buf = buf;
}
/**
* Creates a new {@link MessageBuilder} with the {@code buf} backing buffer.
*
* @param buf
* the backing buffer used to read and write data.
* @return the newly created buffer.
*/
public static MessageBuilder create(ByteBuf buf) {
return new MessageBuilder(buf);
}
/**
* Creates a new {@link MessageBuilder} with the {@code cap} as the
* capacity.
*
* @param cap
* the capacity of the buffer.
* @return the newly created buffer.
*/
public static MessageBuilder create(int cap) {
return MessageBuilder.create(Unpooled.buffer(cap));
}
/**
* Creates a new {@link MessageBuilder} with the default capacity.
*
* @return the newly created buffer.
*/
public static MessageBuilder create() {
return MessageBuilder.create(DEFAULT_CAP);
}
/**
* Prepares the buffer for writing bits.
*/
public void startBitAccess() {
bitIndex = buf.writerIndex() * 8;
}
/**
* Prepares the buffer for writing bytes.
*/
public void endBitAccess() {
buf.writerIndex((bitIndex + 7) / 8);
}
/**
* Builds a new message header.
*
* @param opcode
* the opcode of the message.
* @return an instance of this message builder.
*/
public MessageBuilder newMessage(int opcode) {
put(opcode);
return this;
}
/**
* Builds a new message header for a variable length message. Note that the
* corresponding {@code endVarMessage()} method must be called to finish the
* message.
*
* @param opcode
* the opcode of the message.
* @return an instance of this message builder.
*/
public MessageBuilder newVarMessage(int opcode) {
newMessage(opcode);
varLengthIndex = buf.writerIndex();
put(0);
return this;
}
/**
* Builds a new message header for a variable length message, where the
* length is written as a {@code short} instead of a {@code byte}. Note that
* the corresponding {@code endVarShortMessage()} method must be called to
* finish the message.
*
* @param opcode
* the opcode of the message.
* @return an instance of this message builder.
*/
public MessageBuilder newVarShortMessage(int opcode) {
newMessage(opcode);
varLengthIndex = buf.writerIndex();
putShort(0);
return this;
}
/**
* Finishes a variable message header by writing the actual message length
* at the length {@code byte} position. Call this when the construction of
* the actual variable length message is complete.
*
* @return an instance of this message builder.
*/
public MessageBuilder endVarMessage() {
buf.setByte(varLengthIndex, (byte) (buf.writerIndex() - varLengthIndex - 1));
return this;
}
/**
* Finishes a variable message header by writing the actual message length
* at the length {@code short} position. Call this when the construction of
* the actual variable length message is complete.
*
* @return an instance of this message builder.
*/
public MessageBuilder endVarShortMessage() {
buf.setShort(varLengthIndex, (short) (buf.writerIndex() - varLengthIndex - 2));
return this;
}
/**
* Writes the bytes from the argued buffer into this buffer. This method
* does not modify the argued buffer, and please do not flip the buffer
* beforehand.
*
* @param from
* the argued buffer that bytes will be written from.
* @return an instance of this message builder.
*/
public MessageBuilder putBytes(ByteBuf from) {
for (int i = 0; i < from.writerIndex(); i++) {
put(from.getByte(i));
}
return this;
}
/**
* Writes the bytes from the argued buffer into this buffer.
*
* @param from
* the argued buffer that bytes will be written from.
* @return an instance of this message builder.
*/
public MessageBuilder putBytes(byte[] from, int size) {
buf.writeBytes(from, 0, size);
return this;
}
/**
* Writes the bytes from the argued byte array into this buffer, in reverse.
*
* @param data
* the data to write to this buffer.
*/
public MessageBuilder putBytesReverse(byte[] data) {
for (int i = data.length - 1; i >= 0; i--) {
put(data[i]);
}
return this;
}
/**
* Writes the value as a variable amount of bits.
*
* @param amount
* the amount of bits to write.
* @param value
* the value of the bits.
* @return an instance of this message builder.
* @throws IllegalArgumentException
* if the number of bits is not between {@code 1} and {@code 32}
* inclusive.
*/
public MessageBuilder putBits(int amount, int value) {
if (amount < 0 || amount > 32)
throw new IllegalArgumentException("Number of bits must be " + "between 1 and 32 inclusive.");
int bytePos = bitIndex >> 3;
int bitOffset = 8 - (bitIndex & 7);
bitIndex = bitIndex + amount;
int requiredSpace = bytePos - buf.writerIndex() + 1;
requiredSpace += (amount + 7) / 8;
if (buf.writableBytes() < requiredSpace) {
ByteBuf old = buf;
buf = Unpooled.buffer(old.capacity() + requiredSpace);
buf.writeBytes(old);
}
for (; amount > bitOffset; bitOffset = 8) {
byte tmp = buf.getByte(bytePos);
tmp &= ~BIT_MASK[bitOffset];
tmp |= (value >> (amount - bitOffset)) & BIT_MASK[bitOffset];
buf.setByte(bytePos++, tmp);
amount -= bitOffset;
}
if (amount == bitOffset) {
byte tmp = buf.getByte(bytePos);
tmp &= ~BIT_MASK[bitOffset];
tmp |= value & BIT_MASK[bitOffset];
buf.setByte(bytePos, tmp);
} else {
byte tmp = buf.getByte(bytePos);
tmp &= ~(BIT_MASK[amount] << (bitOffset - amount));
tmp |= (value & BIT_MASK[amount]) << (bitOffset - amount);
buf.setByte(bytePos, tmp);
}
return this;
}
/**
* Writes a boolean bit flag.
*
* @param flag
* the flag to write.
* @return an instance of this message builder.
*/
public MessageBuilder putBit(boolean flag) {
putBits(1, flag ? 1 : 0);
return this;
}
/**
* Writes a value as a {@code byte}.
*
* @param value
* the value to write.
* @param type
* the value type.
* @return an instance of this message builder.
*/
public MessageBuilder put(int value, ValueType type) {
switch (type) {
case A:
value += 128;
break;
case C:
value = -value;
break;
case S:
value = 128 - value;
break;
case STANDARD:
break;
}
buf.writeByte((byte) value);
return this;
}
/**
* Writes a value as a normal {@code byte}.
*
* @param value
* the value to write.
* @return an instance of this message builder.
*/
public MessageBuilder put(int value) {
put(value, ValueType.STANDARD);
return this;
}
/**
* Writes a value as a {@code short}.
*
* @param value
* the value to write.
* @param type
* the value type.
* @param order
* the byte order.
* @return an instance of this message builder.
* @throws IllegalArgumentExcpetion
* if middle or inverse-middle value types are selected.
*/
public MessageBuilder putShort(int value, ValueType type, ByteOrder order) {
switch (order) {
case BIG:
put(value >> 8);
put(value, type);
break;
case MIDDLE:
throw new IllegalArgumentException("Middle-endian short is " + "impossible!");
case INVERSE_MIDDLE:
throw new IllegalArgumentException("Inverse-middle-endian " + "short is impossible!");
case LITTLE:
put(value, type);
put(value >> 8);
break;
}
return this;
}
/**
* Writes a value as a normal big-endian {@code short}.
*
* @param value
* the value to write.
* @return an instance of this message builder.
*/
public MessageBuilder putShort(int value) {
putShort(value, ValueType.STANDARD, ByteOrder.BIG);
return this;
}
/**
* Writes a value as a big-endian {@code short}.
*
* @param value
* the value to write.
* @param type
* the value type.
* @return an instance of this message builder.
*/
public MessageBuilder putShort(int value, ValueType type) {
putShort(value, type, ByteOrder.BIG);
return this;
}
/**
* Writes a value as a standard {@code short}.
*
* @param value
* the value to write.
* @param order
* the byte order.
* @return an instance of this message builder.
*/
public MessageBuilder putShort(int value, ByteOrder order) {
putShort(value, ValueType.STANDARD, order);
return this;
}
/**
* Writes a value as an {@code int}.
*
* @param value
* the value to write.
* @param type
* the value type.
* @param order
* the byte order.
* @return an instance of this message builder.
*/
public MessageBuilder putInt(int value, ValueType type, ByteOrder order) {
switch (order) {
case BIG:
put(value >> 24);
put(value >> 16);
put(value >> 8);
put(value, type);
break;
case MIDDLE:
put(value >> 8);
put(value, type);
put(value >> 24);
put(value >> 16);
break;
case INVERSE_MIDDLE:
put(value >> 16);
put(value >> 24);
put(value, type);
put(value >> 8);
break;
case LITTLE:
put(value, type);
put(value >> 8);
put(value >> 16);
put(value >> 24);
break;
}
return this;
}
/**
* Writes a value as a standard big-endian {@code int}.
*
* @param value
* the value to write.
* @return an instance of this message builder.
*/
public MessageBuilder putInt(int value) {
putInt(value, ValueType.STANDARD, ByteOrder.BIG);
return this;
}
/**
* Writes a value as a big-endian {@code int}.
*
* @param value
* the value to write.
* @param type
* the value type.
* @return an instance of this message builder.
*/
public MessageBuilder putInt(int value, ValueType type) {
putInt(value, type, ByteOrder.BIG);
return this;
}
/**
* Writes a value as a standard {@code int}.
*
* @param value
* the value to write.
* @param order
* the byte order.
* @return an instance of this message builder.
*/
public MessageBuilder putInt(int value, ByteOrder order) {
putInt(value, ValueType.STANDARD, order);
return this;
}
/**
* Writes a value as a {@code long}.
*
* @param value
* the value to write.
* @param type
* the value type.
* @param order
* the byte order.
* @return an instance of this message builder.
* @throws UnsupportedOperationException
* if middle or inverse-middle value types are selected.
*/
public MessageBuilder putLong(long value, ValueType type, ByteOrder order) {
switch (order) {
case BIG:
put((int) (value >> 56));
put((int) (value >> 48));
put((int) (value >> 40));
put((int) (value >> 32));
put((int) (value >> 24));
put((int) (value >> 16));
put((int) (value >> 8));
put((int) value, type);
break;
case MIDDLE:
throw new UnsupportedOperationException("Middle-endian long " + "is not implemented!");
case INVERSE_MIDDLE:
throw new UnsupportedOperationException("Inverse-middle-endian long is not implemented!");
case LITTLE:
put((int) value, type);
put((int) (value >> 8));
put((int) (value >> 16));
put((int) (value >> 24));
put((int) (value >> 32));
put((int) (value >> 40));
put((int) (value >> 48));
put((int) (value >> 56));
break;
}
return this;
}
/**
* Writes a value as a standard big-endian {@code long}.
*
* @param value
* the value to write.
* @return an instance of this message builder.
*/
public MessageBuilder putLong(long value) {
putLong(value, ValueType.STANDARD, ByteOrder.BIG);
return this;
}
/**
* Writes a value as a big-endian {@code long}.
*
* @param value
* the value to write.
* @param type
* the value type.
* @return an instance of this message builder.
*/
public MessageBuilder putLong(long value, ValueType type) {
putLong(value, type, ByteOrder.BIG);
return this;
}
/**
* Writes a value as a standard {@code long}.
*
* @param value
* the value to write.
* @param order
* the byte order to write.
* @return an instance of this message builder.
*/
public MessageBuilder putLong(long value, ByteOrder order) {
putLong(value, ValueType.STANDARD, order);
return this;
}
/**
* Writes a RuneScape {@code String} value.
*
* @param string
* the string to write.
* @return an instance of this message builder.
*/
public MessageBuilder putString(String string) {
for (byte value : string.getBytes()) {
put(value);
}
put(10);
return this;
}
/**
* Reads a value as a {@code byte}.
*
* @param signed
* if the byte is signed.
* @param type
* the value type.
* @return the value of the byte.
*/
public int get(boolean signed, ValueType type) {
int value = buf.readByte();
switch (type) {
case A:
value = value - 128;
break;
case C:
value = -value;
break;
case S:
value = 128 - value;
break;
case STANDARD:
break;
}
return signed ? value : value & 0xff;
}
/**
* Reads a standard signed {@code byte}.
*
* @return the value of the byte.
*/
public int get() {
return get(true, ValueType.STANDARD);
}
/**
* Reads a standard {@code byte}.
*
* @param signed
* if the byte is signed.
* @return the value of the byte.
*/
public int get(boolean signed) {
return get(signed, ValueType.STANDARD);
}
/**
* Reads a signed {@code byte}.
*
* @param type
* the value type.
* @return the value of the byte.
*/
public int get(ValueType type) {
return get(true, type);
}
/**
* Reads a {@code short} value.
*
* @param signed
* if the short is signed.
* @param type
* the value type.
* @param order
* the byte order.
* @return the value of the short.
* @throws UnsupportedOperationException
* if middle or inverse-middle value types are selected.
*/
public int getShort(boolean signed, ValueType type, ByteOrder order) {
int value = 0;
switch (order) {
case BIG:
value |= get(false) << 8;
value |= get(false, type);
break;
case MIDDLE:
throw new UnsupportedOperationException("Middle-endian short " + "is impossible!");
case INVERSE_MIDDLE:
throw new UnsupportedOperationException("Inverse-middle-endian short is impossible!");
case LITTLE:
value |= get(false, type);
value |= get(false) << 8;
break;
}
return signed ? value : value & 0xffff;
}
/**
* Reads a standard signed big-endian {@code short}.
*
* @return the value of the short.
*/
public int getShort() {
return getShort(true, ValueType.STANDARD, ByteOrder.BIG);
}
/**
* Reads a standard big-endian {@code short}.
*
* @param signed
* if the short is signed.
* @return the value of the short.
*/
public int getShort(boolean signed) {
return getShort(signed, ValueType.STANDARD, ByteOrder.BIG);
}
/**
* Reads a signed big-endian {@code short}.
*
* @param type
* the value type.
* @return the value of the short.
*/
public int getShort(ValueType type) {
return getShort(true, type, ByteOrder.BIG);
}
/**
* Reads a big-endian {@code short}.
*
* @param signed
* if the short is signed.
* @param type
* the value type.
* @return the value of the short.
*/
public int getShort(boolean signed, ValueType type) {
return getShort(signed, type, ByteOrder.BIG);
}
/**
* Reads a signed standard {@code short}.
*
* @param order
* the byte order.
* @return the value of the short.
*/
public int getShort(ByteOrder order) {
return getShort(true, ValueType.STANDARD, order);
}
/**
* Reads a standard {@code short}.
*
* @param signed
* if the short is signed.
* @param order
* the byte order.
* @return the value of the short.
*/
public int getShort(boolean signed, ByteOrder order) {
return getShort(signed, ValueType.STANDARD, order);
}
/**
* Reads a signed {@code short}.
*
* @param type
* the value type.
* @param order
* the byte order.
* @return the value of the short.
*/
public int getShort(ValueType type, ByteOrder order) {
return getShort(true, type, order);
}
/**
* Reads an {@code int}.
*
* @param signed
* if the integer is signed.
* @param type
* the value type.
* @param order
* the byte order.
* @return the value of the integer.
*/
public int getInt(boolean signed, ValueType type, ByteOrder order) {
long value = 0;
switch (order) {
case BIG:
value |= get(false) << 24;
value |= get(false) << 16;
value |= get(false) << 8;
value |= get(false, type);
break;
case MIDDLE:
value |= get(false) << 8;
value |= get(false, type);
value |= get(false) << 24;
value |= get(false) << 16;
break;
case INVERSE_MIDDLE:
value |= get(false) << 16;
value |= get(false) << 24;
value |= get(false, type);
value |= get(false) << 8;
break;
case LITTLE:
value |= get(false, type);
value |= get(false) << 8;
value |= get(false) << 16;
value |= get(false) << 24;
break;
}
return (int) (signed ? value : value & 0xffffffffL);
}
/**
* Reads a signed standard big-endian {@code int}.
*
* @return the value of the integer.
*/
public int getInt() {
return getInt(true, ValueType.STANDARD, ByteOrder.BIG);
}
/**
* Reads a standard big-endian {@code int}.
*
* @param signed
* if the integer is signed.
* @return the value of the integer.
*/
public int getInt(boolean signed) {
return getInt(signed, ValueType.STANDARD, ByteOrder.BIG);
}
/**
* Reads a signed big-endian {@code int}.
*
* @param type
* the value type.
* @return the value of the integer.
*/
public int getInt(ValueType type) {
return getInt(true, type, ByteOrder.BIG);
}
/**
* Reads a big-endian {@code int}.
*
* @param signed
* if the integer is signed.
* @param type
* the value type.
* @return the value of the integer.
*/
public int getInt(boolean signed, ValueType type) {
return getInt(signed, type, ByteOrder.BIG);
}
/**
* Reads a signed standard {@code int}.
*
* @param order
* the byte order.
* @return the value of the integer.
*/
public int getInt(ByteOrder order) {
return getInt(true, ValueType.STANDARD, order);
}
/**
* Reads a standard {@code int}.
*
* @param signed
* if the integer is signed.
* @param order
* the byte order.
* @return the value of the integer.
*/
public int getInt(boolean signed, ByteOrder order) {
return getInt(signed, ValueType.STANDARD, order);
}
/**
* Reads a signed {@code int}.
*
* @param type
* the value type.
* @param order
* the byte order.
* @return the value of the integer.
*/
public int getInt(ValueType type, ByteOrder order) {
return getInt(true, type, order);
}
/**
* Reads a signed {@code long} value.
*
* @param type
* the value type.
* @param order
* the byte order.
* @return the value of the long.
* @throws UnsupportedOperationException
* if middle or inverse-middle value types are selected.
*/
public long getLong(ValueType type, ByteOrder order) {
long value = 0;
switch (order) {
case BIG:
value |= (long) get(false) << 56L;
value |= (long) get(false) << 48L;
value |= (long) get(false) << 40L;
value |= (long) get(false) << 32L;
value |= (long) get(false) << 24L;
value |= (long) get(false) << 16L;
value |= (long) get(false) << 8L;
value |= get(false, type);
break;
case INVERSE_MIDDLE:
case MIDDLE:
throw new UnsupportedOperationException("Middle and " + "inverse-middle value types not supported!");
case LITTLE:
value |= get(false, type);
value |= (long) get(false) << 8L;
value |= (long) get(false) << 16L;
value |= (long) get(false) << 24L;
value |= (long) get(false) << 32L;
value |= (long) get(false) << 40L;
value |= (long) get(false) << 48L;
value |= (long) get(false) << 56L;
break;
}
return value;
}
/**
* Reads a signed standard big-endian {@code long}.
*
* @return the value of the long.
*/
public long getLong() {
return getLong(ValueType.STANDARD, ByteOrder.BIG);
}
/**
* Reads a signed big-endian {@code long}.
*
* @param type
* the value type
* @return the value of the long.
*/
public long getLong(ValueType type) {
return getLong(type, ByteOrder.BIG);
}
/**
* Reads a signed standard {@code long}.
*
* @param order
* the byte order
* @return the value of the long.
*/
public long getLong(ByteOrder order) {
return getLong(ValueType.STANDARD, order);
}
/**
* Reads a RuneScape {@code String} value.
*
* @return the value of the string.
*/
public String getString() {
byte temp;
StringBuilder b = new StringBuilder();
while ((temp = (byte) get()) != 10) {
b.append((char) temp);
}
return b.toString();
}
/**
* Reads the amount of bytes into the array, starting at the current
* position.
*
* @param amount
* the amount to read.
* @return a buffer filled with the data.
*/
public byte[] getBytes(int amount) {
return getBytes(amount, ValueType.STANDARD);
}
/**
* Reads the amount of bytes into a byte array, starting at the current
* position.
*
* @param amount
* the amount of bytes.
* @param type
* the value type of each byte.
* @return a buffer filled with the data.
*/
public byte[] getBytes(int amount, ValueType type) {
byte[] data = new byte[amount];
for (int i = 0; i < amount; i++) {
data[i] = (byte) get(type);
}
return data;
}
/**
* Reads the amount of bytes from the buffer in reverse, starting at
* {@code current_position + amount} and reading in reverse until the
* current position.
*
* @param amount
* the amount of bytes to read.
* @param type
* the value type of each byte.
* @return a buffer filled with the data.
*/
public byte[] getBytesReverse(int amount, ValueType type) {
byte[] data = new byte[amount];
int dataPosition = 0;
for (int i = buf.readerIndex() + amount - 1; i >= buf.readerIndex(); i--) {
int value = buf.getByte(i);
switch (type) {
case A:
value -= 128;
break;
case C:
value = -value;
break;
case S:
value = 128 - value;
break;
case STANDARD:
break;
}
data[dataPosition++] = (byte) value;
}
return data;
}
/**
* Gets the backing byte buffer used to read and write data.
*
* @return the backing byte buffer.
*/
public ByteBuf buffer() {
return buf;
}
}