package seng302.gameServer.messages; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Arrays; import java.util.zip.CRC32; public abstract class Message { private final int CRC_SIZE = 4; private Header header; private ByteBuffer buffer; private int bufferPosition; private CRC32 crc; /** * @param header Set the header for this message */ void setHeader(Header header){ this.header = header; } /** * @return the header specified for this message */ Header getHeader(){ return header; } /** * @return the size of the message */ public abstract int getSize(); /** * Allocate byte buffer to correct size */ void allocateBuffer(){ buffer = ByteBuffer.allocate(Header.getSize() + getSize() + CRC_SIZE); buffer.order(ByteOrder.LITTLE_ENDIAN); bufferPosition = 0; buffer.position(bufferPosition); } /** * Write the set header to the byte buffer */ void writeHeaderToBuffer(){ buffer.put(getHeader().getByteBuffer().array()); bufferPosition += Header.getSize(); buffer.position(bufferPosition); } /** * Move the buffer position by n bytes * @param size Number of bytes to move the buffer by */ private void moveBufferPositionBy(int size){ bufferPosition += size; buffer.position(bufferPosition); } /** * Put an unsigned byte in the buffer */ void putUnsignedByte(byte b){ buffer.put(ByteBuffer.allocate(1).put((byte) (b & 0xff)).array()); moveBufferPositionBy(1); } /** * Put an signed byte in the buffer */ void putByte(byte b){ buffer.put(ByteBuffer.allocate(1).put(b).array()); moveBufferPositionBy(1); } /** * Place an unsigned integer of the specified length in the buffer * @param val The integer value to add (Note: This must be long due to java not supporting unsigned integers) * @param size The size of the int to be added to the buffer */ void putUnsignedInt(long val, int size){ if (size <= 1){ putUnsignedByte((byte) val); } else if (size < 4){ // Use short byte[] tmp = Message.intToByteArray(val, size); //ByteBuffer.allocate(size).putShort((short) (val & 0xffff)).array(); reverse(tmp); buffer.put(tmp); moveBufferPositionBy(size); } else{ // Use int byte[] tmp = Message.intToByteArray(val, size); reverse(tmp); moveBufferPositionBy(size); } } /** * Put a signed int of a specified length in the buffer * @param val The integer value to add * @param size The size of the integer to be added to the buffer */ void putInt(long val, int size){ if (size < 4){ byte[] tmp = Message.intToByteArray(val, size); reverse(tmp); buffer.put(tmp); } else{ byte[] tmp = Message.intToByteArray(val, size); reverse(tmp); buffer.put(tmp); } moveBufferPositionBy(size); } /** * Write an array of bytes to the buffer * @param bytes to write */ void putBytes(byte[] bytes){ buffer.put(bytes); moveBufferPositionBy(bytes.length); } /** * Write a ByteBuffer of bytes to the buffer * @param bytes to write * @param size number of bytes */ void putBytes(ByteBuffer bytes, int size){ buffer.put(bytes.array()); moveBufferPositionBy(size); } /** * Calculate the CRC of the buffer and append it to the end of the buffer */ void writeCRC(){ crc = new CRC32(); buffer.position(0); byte[] data = Arrays.copyOfRange(buffer.array(), 0, buffer.array().length-CRC_SIZE); crc.update(data); buffer.position(bufferPosition); putInt((int) crc.getValue(), CRC_SIZE); } /** * @return The current buffer as a byte array */ public byte[] getBuffer(){ return buffer.array(); } /** * Rewind the buffer to the beginning */ void rewind(){ buffer.flip(); } /** * Convert an integer to an array of bytes * @param val The value to add * @param len The width of the integer in the buffer * @return A byte array to be sent */ public static byte[] intToByteArray(long val, int len){ int index = 0; byte[] data = new byte[len]; for (int i = 0; i < len; i++){ data[len - index - 1] = (byte) (val & 0xFF); val >>>= 8; index++; } return data; } /** * takes an array of up to 7 bytes in little endian format and * returns a positive long constructed from the input bytes * * @param bytes the bytes to be converted to long * @return a positive long if there is less than 8 bytes -1 otherwise */ public static long bytesToLong(byte[] bytes){ long partialLong = 0; int index = 0; for (byte b: bytes){ if (index > 6){ return -1; } partialLong = partialLong | (b & 0xFFL) << (index * 8); index++; } return partialLong; } /** * Reverse an array of bytes * @param data The byte[] to reverse */ public static void reverse(byte[] data) { for (int left = 0, right = data.length - 1; left < right; left++, right--) { byte temp = (byte) (data[left] & 0xff); data[left] = (byte) (data[right] & 0xff); data[right] = (byte) (temp & 0xff); } } }