diff --git a/pom.xml b/pom.xml index 488df03163..e95d8b9b3c 100644 --- a/pom.xml +++ b/pom.xml @@ -61,6 +61,11 @@ netty-codec 4.1.65.Final + + io.netty + netty-buffer + 4.1.65.Final + @@ -96,6 +101,20 @@ js-scriptengine ${graalvm.version} + + + + org.junit.jupiter + junit-jupiter-api + 5.7.2 + + + org.junit.jupiter + junit-jupiter-engine + 5.7.2 + + + diff --git a/src/main/java/net/packet/ByteBufInPacket.java b/src/main/java/net/packet/ByteBufInPacket.java new file mode 100644 index 0000000000..0977172354 --- /dev/null +++ b/src/main/java/net/packet/ByteBufInPacket.java @@ -0,0 +1,81 @@ +package net.packet; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; + +import java.awt.*; + +public class ByteBufInPacket implements InPacket { + private final ByteBuf byteBuf; + + public ByteBufInPacket(ByteBuf byteBuf) { + this.byteBuf = byteBuf; + } + + @Override + public byte[] getBytes() { + return ByteBufUtil.getBytes(byteBuf); + } + + @Override + public byte readByte() { + return byteBuf.readByte(); + } + + @Override + public short readShort() { + return byteBuf.readShortLE(); + } + + @Override + public int readInt() { + return byteBuf.readIntLE(); + } + + @Override + public long readLong() { + return byteBuf.readLongLE(); + } + + @Override + public Point readPoint() { + final short x = byteBuf.readShortLE(); + final short y = byteBuf.readShortLE(); + return new Point(x, y); + } + + @Override + public String readString() { + short length = readShort(); + byte[] stringBytes = new byte[length]; + byteBuf.readBytes(stringBytes); + return new String(stringBytes, STRING_CHARSET); + } + + @Override + public byte[] readBytes(int numberOfBytes) { + byte[] bytes = new byte[numberOfBytes]; + byteBuf.readBytes(bytes); + return bytes; + } + + @Override + public void skip(int numberOfBytes) { + byteBuf.skipBytes(numberOfBytes); + } + + @Override + public int available() { + return byteBuf.readableBytes(); + } + + @Override + public void seek(int byteOffset) { + byteBuf.readerIndex(byteOffset); + } + + @Override + public int getPosition() { + return byteBuf.readerIndex(); + } +} diff --git a/src/main/java/net/packet/ByteBufOutPacket.java b/src/main/java/net/packet/ByteBufOutPacket.java new file mode 100644 index 0000000000..5aa7a20b79 --- /dev/null +++ b/src/main/java/net/packet/ByteBufOutPacket.java @@ -0,0 +1,83 @@ +package net.packet; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import net.jcip.annotations.NotThreadSafe; +import net.opcodes.SendOpcode; + +import java.awt.*; + +@NotThreadSafe +public class ByteBufOutPacket implements OutPacket { + private final ByteBuf byteBuf; + + public ByteBufOutPacket(SendOpcode op) { + ByteBuf byteBuf = Unpooled.buffer(); + byteBuf.writeShortLE((short) op.getValue()); + this.byteBuf = byteBuf; + } + + public ByteBufOutPacket(SendOpcode op, int initialCapacity) { + ByteBuf byteBuf = Unpooled.buffer(initialCapacity); + byteBuf.writeShortLE((short) op.getValue()); + this.byteBuf = byteBuf; + } + + @Override + public byte[] getBytes() { + return ByteBufUtil.getBytes(byteBuf); + } + + @Override + public void writeByte(byte value) { + byteBuf.writeByte(value); + } + + @Override + public void writeByte(int value) { + writeByte((byte) value); + } + + @Override + public void writeBytes(byte[] value) { + byteBuf.writeBytes(value); + } + + @Override + public void writeShort(int value) { + byteBuf.writeShortLE(value); + } + + @Override + public void writeInt(int value) { + byteBuf.writeIntLE(value); + } + + @Override + public void writeLong(long value) { + byteBuf.writeLongLE(value); + } + + @Override + public void writeBoolean(boolean value) { + byteBuf.writeByte(value ? 1 : 0); + } + + @Override + public void writeString(String value) { + writeShort((short) value.length()); + writeBytes(value.getBytes(STRING_CHARSET)); + } + + @Override + public void writePoint(Point value) { + writeShort((short) value.getX()); + writeShort((short) value.getY()); + } + + @Override + public void skip(int numberOfBytes) { + writeBytes(new byte[numberOfBytes]); + } +} diff --git a/src/main/java/net/packet/InPacket.java b/src/main/java/net/packet/InPacket.java new file mode 100644 index 0000000000..2c899c2ade --- /dev/null +++ b/src/main/java/net/packet/InPacket.java @@ -0,0 +1,17 @@ +package net.packet; + +import java.awt.*; + +public interface InPacket extends Packet { + byte readByte(); + short readShort(); + int readInt(); + long readLong(); + Point readPoint(); + String readString(); + byte[] readBytes(int numberOfBytes); + void skip(int numberOfBytes); + int available(); + void seek(int byteOffset); + int getPosition(); +} diff --git a/src/main/java/net/packet/OutPacket.java b/src/main/java/net/packet/OutPacket.java new file mode 100644 index 0000000000..57e953a6f7 --- /dev/null +++ b/src/main/java/net/packet/OutPacket.java @@ -0,0 +1,16 @@ +package net.packet; + +import java.awt.*; + +public interface OutPacket extends Packet { + void writeByte(byte value); + void writeByte(int value); + void writeBytes(byte[] value); + void writeShort(int value); + void writeInt(int value); + void writeLong(long value); + void writeBoolean(boolean value); + void writeString(String value); + void writePoint(Point value); + void skip(int numberOfBytes); +} diff --git a/src/main/java/net/packet/Packet.java b/src/main/java/net/packet/Packet.java new file mode 100644 index 0000000000..79f257cd38 --- /dev/null +++ b/src/main/java/net/packet/Packet.java @@ -0,0 +1,10 @@ +package net.packet; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +public interface Packet { + Charset STRING_CHARSET = StandardCharsets.US_ASCII; + + byte[] getBytes(); +} diff --git a/src/test/java/net/packet/ByteBufInPacketTest.java b/src/test/java/net/packet/ByteBufInPacketTest.java new file mode 100644 index 0000000000..80a611abd8 --- /dev/null +++ b/src/test/java/net/packet/ByteBufInPacketTest.java @@ -0,0 +1,192 @@ +package net.packet; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.awt.*; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ByteBufInPacketTest { + private ByteBuf byteBuf; + private InPacket inPacket; + + @BeforeEach + void reset() { + this.byteBuf = Unpooled.buffer(); + this.inPacket = new ByteBufInPacket(byteBuf); + } + + private void givenWrittenBytes(int... bytes) { + for (int b : bytes) { + byteBuf.writeByte(b); + } + } + + @Test + void readByte() { + final byte writtenByte = 123; + byteBuf.writeByte(writtenByte); + + byte readByte = inPacket.readByte(); + + assertEquals(writtenByte, readByte); + } + + @Test + void readShort() { + final short writtenShort = 12_345; + byteBuf.writeShortLE(writtenShort); + + short readShort = inPacket.readShort(); + + assertEquals(writtenShort, readShort); + } + + @Test + void readInt() { + final int writtenInt = 1_234_567_890; + byteBuf.writeIntLE(writtenInt); + + int readInt = inPacket.readInt(); + + assertEquals(writtenInt, readInt); + } + + @Test + void readLong() { + final long writtenLong = 9_223_372_036_854_775_807L; + byteBuf.writeLongLE(writtenLong); + + long readLong = inPacket.readLong(); + + assertEquals(writtenLong, readLong); + } + + @Test + void readPoint() { + final Point writtenPoint = new Point(111, 222); + byteBuf.writeShortLE((short) writtenPoint.getX()); + byteBuf.writeShortLE((short) writtenPoint.getY()); + + Point readPoint = inPacket.readPoint(); + + assertEquals(writtenPoint, readPoint); + } + + @Test + void readString() { + final String writtenString = "You have gained experience (+3200)"; + byteBuf.writeShortLE(writtenString.length()); + byte[] writtenStringBytes = writtenString.getBytes(Packet.STRING_CHARSET); + byteBuf.writeBytes(writtenStringBytes); + + String readString = inPacket.readString(); + + assertEquals(writtenString, readString); + } + + @Test + void readBytes() { + givenWrittenBytes(10, 11, 12, 13, 14, 15); + + byte[] byteBatch1 = inPacket.readBytes(1); + assertEquals(1, byteBatch1.length); + assertEquals(10, byteBatch1[0]); + + byte[] byteBatch2 = inPacket.readBytes(2); + assertEquals(2, byteBatch2.length); + assertEquals(11, byteBatch2[0]); + assertEquals(12, byteBatch2[1]); + + byte[] byteBatch3 = inPacket.readBytes(3); + assertEquals(3, byteBatch3.length); + assertEquals(13, byteBatch3[0]); + assertEquals(14, byteBatch3[1]); + assertEquals(15, byteBatch3[2]); + } + + @Test + void skip() { + givenWrittenBytes(20, 21, 22, 23, 24, 25); + + byte firstByte = inPacket.readByte(); + assertEquals(20, firstByte); + + inPacket.skip(3); + + byte fifthByte = inPacket.readByte(); + assertEquals(24, fifthByte); + } + + @Test + void available() { + givenWrittenBytes(30, 31, 32, 33, 34, 35); + + assertEquals(6, inPacket.available()); + + inPacket.readByte(); + assertEquals(5, inPacket.available()); + + inPacket.readInt(); + assertEquals(1, inPacket.available()); + } + + @Test + void seek() { + givenWrittenBytes(40, 41, 42, 43, 44, 45); + + inPacket.seek(2); + assertEquals(4, inPacket.available()); + byte byteAtSeek = inPacket.readByte(); + assertEquals(42, byteAtSeek); + + inPacket.seek(0); + byte byteAtReset = inPacket.readByte(); + assertEquals(40, byteAtReset); + } + + @Test + void getPosition() { + givenWrittenBytes(50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60); + + assertEquals(0, inPacket.getPosition()); + + inPacket.readByte(); + assertEquals(1, inPacket.getPosition()); + + inPacket.readShort(); + assertEquals(3, inPacket.getPosition()); + + inPacket.readInt(); + assertEquals(7, inPacket.getPosition()); + + inPacket.seek(5); + assertEquals(5, inPacket.getPosition()); + } + + @Test + void getBytes() { + givenWrittenBytes(20, 19, 21, 18, 22); + + byte[] bytes = inPacket.getBytes(); + + assertArrayEquals(new byte[]{20, 19, 21, 18, 22}, bytes); + } + + @Test + void whenGetBytes_shouldBeRepeatable() { + givenWrittenBytes(1, 2, 3, 4, 5); + + byte[] bytes = inPacket.getBytes(); + assertEquals(5, bytes.length); + + byte[] sameBytes = inPacket.getBytes(); + assertEquals(5, sameBytes.length); + + assertArrayEquals(bytes, sameBytes); + } +} \ No newline at end of file diff --git a/src/test/java/net/packet/ByteBufOutPacketTest.java b/src/test/java/net/packet/ByteBufOutPacketTest.java new file mode 100644 index 0000000000..3673254736 --- /dev/null +++ b/src/test/java/net/packet/ByteBufOutPacketTest.java @@ -0,0 +1,206 @@ +package net.packet; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import net.opcodes.SendOpcode; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.awt.*; +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ByteBufOutPacketTest { + private ByteBufOutPacket outPacket; + + @BeforeEach + void reset() { + outPacket = new ByteBufOutPacket(SendOpcode.ADMIN_SHOP); // Any opcode will do + } + + private static ByteBuf wrapExplicitlyWrittenBytes(OutPacket outPacket) { + byte[] packetBytes = outPacket.getBytes(); + ByteBuf byteBuf = Unpooled.copiedBuffer(packetBytes); + byteBuf.readShortLE(); // Skip over opcode + return byteBuf; + } + + @Test + void whenInstantiatingNew_shouldWriteOpcode() { + byte[] packetBytes = new ByteBufOutPacket(SendOpcode.NPC_TALK).getBytes(); + assertEquals(2, packetBytes.length); + } + + @Test + void getBytes() { + ByteBufOutPacket outPacket = new ByteBufOutPacket(SendOpcode.PING); // This opcode has value 0x11 = 17 in decimal + outPacket.writeByte(10); + outPacket.writeByte(20); + outPacket.writeByte(30); + + byte[] bytes = outPacket.getBytes(); + + assertArrayEquals(new byte[]{(byte) 17, (byte) 0, (byte) 10, (byte) 20, (byte) 30}, bytes); + } + + @Test + void writeByte() { + final byte writtenByte = 19; + outPacket.writeByte(writtenByte); + + ByteBuf wrapped = wrapExplicitlyWrittenBytes(outPacket); + byte readByte = wrapped.readByte(); + + assertEquals(writtenByte, readByte); + } + + @Test + void writeByteFromInt() { + final int writtenInt = 123; + outPacket.writeByte(writtenInt); + + ByteBuf wrapped = wrapExplicitlyWrittenBytes(outPacket); + byte readByte = wrapped.readByte(); + + assertEquals(writtenInt, readByte); + } + + @Test + void whenWritingByteFromInt_shouldOnlyWrite1Byte() { + final int writtenInt = Integer.MAX_VALUE; + outPacket.writeByte(writtenInt); + + byte[] bytes = outPacket.getBytes(); + assertEquals(2 + 1, bytes.length); // 2 for opcode + } + + @Test + void writeBytes() { + byte[] writtenBytes = {101, 102, 103}; + outPacket.writeBytes(writtenBytes); + + ByteBuf wrapped = wrapExplicitlyWrittenBytes(outPacket); + + assertEquals(101, wrapped.readByte()); + assertEquals(102, wrapped.readByte()); + assertEquals(103, wrapped.readByte()); + } + + @Test + void writeShort() { + final short writtenShort = 4312; + outPacket.writeShort(writtenShort); + + ByteBuf wrapped = wrapExplicitlyWrittenBytes(outPacket); + short readShort = wrapped.readShortLE(); + + assertEquals(writtenShort, readShort); + } + + @Test + void whenWritingShortFromInt_shouldOnlyWrite2Bytes() { + final int writtenInt = Integer.MAX_VALUE; + outPacket.writeShort(writtenInt); + + byte[] bytes = outPacket.getBytes(); + assertEquals(2 + 2, bytes.length); // 2 for opcode + } + + @Test + void writeShortFromInt() { + final int writtenInt = 34_567; + outPacket.writeShort(writtenInt); + + ByteBuf wrapped = wrapExplicitlyWrittenBytes(outPacket); + short readShort = wrapped.readShortLE(); + + assertEquals((short) writtenInt, readShort); + } + + @Test + void writeInt() { + final int writtenInt = 1_010_101_010; + outPacket.writeInt(writtenInt); + + ByteBuf wrapped = wrapExplicitlyWrittenBytes(outPacket); + int readInt = wrapped.readIntLE(); + + assertEquals(writtenInt, readInt); + } + + @Test + void writeLong() { + final long writtenLong = 100_200_300_400_500_600L; + outPacket.writeLong(writtenLong); + + ByteBuf wrapped = wrapExplicitlyWrittenBytes(outPacket); + long readLong = wrapped.readLongLE(); + + assertEquals(writtenLong, readLong); + } + + @Test + void writeBoolean_true() { + outPacket.writeBoolean(true); + + ByteBuf wrapped = wrapExplicitlyWrittenBytes(outPacket); + byte readByte = wrapped.readByte(); + + assertEquals(1, readByte); + } + + @Test + void writeBoolean_false() { + outPacket.writeBoolean(false); + + ByteBuf wrapped = wrapExplicitlyWrittenBytes(outPacket); + byte readByte = wrapped.readByte(); + + assertEquals(0, readByte); + } + + @Test + void writeString() { + final String writtenString = "You've been weakened, making you unable to jump."; + outPacket.writeString(writtenString); + + ByteBuf wrapped = wrapExplicitlyWrittenBytes(outPacket); + int length = wrapped.readShortLE(); + byte[] stringBytes = new byte[length]; + wrapped.readBytes(stringBytes); + String readString = new String(stringBytes, StandardCharsets.US_ASCII); + + assertEquals(writtenString, readString); + } + + @Test + void writePoint() { + final Point writtenPoint = new Point(23, 42); + outPacket.writePoint(writtenPoint); + + ByteBuf wrapped = wrapExplicitlyWrittenBytes(outPacket); + short readX = wrapped.readShortLE(); + short readY = wrapped.readShortLE(); + + assertEquals((short) writtenPoint.getX(), readX); + assertEquals((short) writtenPoint.getY(), readY); + } + + @Test + void whenSkipping_shouldWriteZeroes() { + final byte firstWrittenByte = 9; + final byte secondWrittenByte = 11; + outPacket.writeByte(firstWrittenByte); + outPacket.skip(2); + outPacket.writeByte(secondWrittenByte); + + ByteBuf wrapped = wrapExplicitlyWrittenBytes(outPacket); + + assertEquals(firstWrittenByte, wrapped.readByte()); + assertEquals(0, wrapped.readByte()); + assertEquals(0, wrapped.readByte()); + assertEquals(secondWrittenByte, wrapped.readByte()); + } +} \ No newline at end of file