Merge branch 'feature/net-io-stream' into development
This commit is contained in:
10
build.gradle
10
build.gradle
@@ -17,6 +17,7 @@ repositories {
|
|||||||
ext {
|
ext {
|
||||||
slf4j_version = '1.7.25'
|
slf4j_version = '1.7.25'
|
||||||
lombok_version = '1.18.2'
|
lombok_version = '1.18.2'
|
||||||
|
junit_version = '5.5.2'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@@ -29,5 +30,14 @@ dependencies {
|
|||||||
/* LOMBOK */
|
/* LOMBOK */
|
||||||
annotationProcessor (group: 'org.projectlombok', name: 'lombok', version: lombok_version)
|
annotationProcessor (group: 'org.projectlombok', name: 'lombok', version: lombok_version)
|
||||||
compileOnly (group: 'org.projectlombok', name: 'lombok', version: lombok_version)
|
compileOnly (group: 'org.projectlombok', name: 'lombok', version: lombok_version)
|
||||||
|
|
||||||
|
/* TESTING */
|
||||||
|
testImplementation (group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: junit_version)
|
||||||
|
testRuntimeOnly (group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: junit_version)
|
||||||
|
testImplementation (group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: junit_version)
|
||||||
|
testImplementation (group: 'org.apache.commons', name: 'commons-lang3', version: '3.9')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
|
|||||||
63
src/main/java/mc/protocol/io/ByteArrayNetOutputStream.java
Normal file
63
src/main/java/mc/protocol/io/ByteArrayNetOutputStream.java
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package mc.protocol.io;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
|
||||||
|
public class ByteArrayNetOutputStream extends NetOutputStream {
|
||||||
|
|
||||||
|
//TODO может заменить на DataOutputStream?
|
||||||
|
private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeByte(int value) {
|
||||||
|
baos.write(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeBytes(byte[] buffer, int offset, int lengtn) {
|
||||||
|
baos.write(buffer, offset, lengtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeShort(int value) {
|
||||||
|
baos.write((value >>> 8) & 0xFF);
|
||||||
|
baos.write(value & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeInt(int value) {
|
||||||
|
baos.write((value >>> 24) & 0xFF);
|
||||||
|
baos.write((value >>> 16) & 0xFF);
|
||||||
|
baos.write((value >>> 8) & 0xFF);
|
||||||
|
baos.write(value & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeLong(long value) {
|
||||||
|
baos.write((int) ((value >>> 56) & 0xFF));
|
||||||
|
baos.write((int) ((value >>> 48) & 0xFF));
|
||||||
|
baos.write((int) ((value >>> 40) & 0xFF));
|
||||||
|
baos.write((int) ((value >>> 32) & 0xFF));
|
||||||
|
baos.write((int) ((value >>> 24) & 0xFF));
|
||||||
|
baos.write((int) ((value >>> 16) & 0xFF));
|
||||||
|
baos.write((int) ((value >>> 8) & 0xFF));
|
||||||
|
baos.write((int) (value & 0xFF));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeFloat(float value) {
|
||||||
|
writeInt(Float.floatToIntBits(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeDouble(double value) {
|
||||||
|
writeLong(Double.doubleToLongBits(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
return baos.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] toByteArray() {
|
||||||
|
return baos.toByteArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package mc.protocol.io;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
public abstract class NetInputStream extends InputStream {
|
public abstract class NetInputStream extends InputStream {
|
||||||
|
|
||||||
@@ -11,11 +12,13 @@ public abstract class NetInputStream extends InputStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read() throws IOException {
|
public int read() {
|
||||||
return readByte();
|
return readByte();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unsigned Byte [1]
|
public int readUnsignedByte() {
|
||||||
|
return readByte() & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
public int readUnsignedShort() {
|
public int readUnsignedShort() {
|
||||||
return readShort() & 0xFFFF;
|
return readShort() & 0xFFFF;
|
||||||
@@ -44,14 +47,31 @@ public abstract class NetInputStream extends InputStream {
|
|||||||
throw new DecoderException("String length less zero!");
|
throw new DecoderException("String length less zero!");
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] bytes = new byte[length];
|
byte[] bytes = new byte[length * 4];
|
||||||
readBytes(bytes);
|
int readbleBytes = 0;
|
||||||
return new String(bytes, StandardCharsets.UTF_8);
|
for (int i = 0; i < length; i++) {
|
||||||
|
byte b = readByte();
|
||||||
|
bytes[readbleBytes++] = b;
|
||||||
|
|
||||||
|
switch ((b & 0xFF) >> 4) {
|
||||||
|
case 0b1100:
|
||||||
|
case 0b1101:
|
||||||
|
bytes[readbleBytes++] = readByte();
|
||||||
|
break;
|
||||||
|
case 0b1110:
|
||||||
|
bytes[readbleBytes++] = readByte();
|
||||||
|
bytes[readbleBytes++] = readByte();
|
||||||
|
break;
|
||||||
|
case 0b1111:
|
||||||
|
bytes[readbleBytes++] = readByte();
|
||||||
|
bytes[readbleBytes++] = readByte();
|
||||||
|
bytes[readbleBytes++] = readByte();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chat
|
return new String(bytes, 0, readbleBytes, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
// Identifier
|
|
||||||
|
|
||||||
public int readVarInt() {
|
public int readVarInt() {
|
||||||
int numRead = 0;
|
int numRead = 0;
|
||||||
@@ -72,7 +92,25 @@ public abstract class NetInputStream extends InputStream {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// VarLong
|
public long readVarLong() {
|
||||||
|
int numRead = 0;
|
||||||
|
long result = 0L;
|
||||||
|
byte read;
|
||||||
|
do {
|
||||||
|
if (numRead > 10) {
|
||||||
|
//FIXME выводить в лог предупреждение
|
||||||
|
break; // VarLong is too big
|
||||||
|
}
|
||||||
|
|
||||||
|
read = readByte();
|
||||||
|
long value = (read & 0b01111111);
|
||||||
|
result |= (value << (7 * numRead));
|
||||||
|
|
||||||
|
numRead++;
|
||||||
|
} while ((read & 0b10000000) != 0);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// Entity Metadata
|
// Entity Metadata
|
||||||
|
|
||||||
@@ -84,7 +122,9 @@ public abstract class NetInputStream extends InputStream {
|
|||||||
|
|
||||||
// Angle [1]
|
// Angle [1]
|
||||||
|
|
||||||
// UUID [16]
|
public UUID readUUID() {
|
||||||
|
return new UUID(readLong(), readLong());
|
||||||
|
}
|
||||||
|
|
||||||
public int readBytes(byte[] buffer) {
|
public int readBytes(byte[] buffer) {
|
||||||
return readBytes(buffer, 0, buffer.length);
|
return readBytes(buffer, 0, buffer.length);
|
||||||
@@ -100,9 +140,23 @@ public abstract class NetInputStream extends InputStream {
|
|||||||
return readBytes(buffer, offset, length);
|
return readBytes(buffer, offset, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract void markReadIndex();
|
||||||
|
|
||||||
|
public abstract void resetReadIndex();
|
||||||
|
|
||||||
|
public abstract int readableBytes();
|
||||||
|
|
||||||
public abstract byte readByte();
|
public abstract byte readByte();
|
||||||
|
|
||||||
public abstract int readBytes(byte[] buffer, int offset, int lengtn);
|
public abstract int readBytes(byte[] buffer, int offset, int lengtn);
|
||||||
|
|
||||||
public abstract int readShort();
|
public abstract int readShort();
|
||||||
|
|
||||||
|
public abstract int readInt();
|
||||||
|
|
||||||
|
public abstract long readLong();
|
||||||
|
|
||||||
|
public abstract float readFloat();
|
||||||
|
|
||||||
|
public abstract double readDouble();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package mc.protocol.io;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
public abstract class NetOutputStream extends OutputStream {
|
public abstract class NetOutputStream extends OutputStream {
|
||||||
|
|
||||||
@@ -29,14 +30,15 @@ public abstract class NetOutputStream extends OutputStream {
|
|||||||
|
|
||||||
public void writeString(String string) {
|
public void writeString(String string) {
|
||||||
byte[] buf;
|
byte[] buf;
|
||||||
|
int length = (int) string.codePoints().count();
|
||||||
|
|
||||||
if (string.length() > Short.MAX_VALUE) {
|
if (length > Short.MAX_VALUE) {
|
||||||
//FIXME нужно выдавать предупреждение в лог
|
//FIXME нужно выдавать предупреждение в лог
|
||||||
buf = string.substring(0, Short.MAX_VALUE).getBytes(StandardCharsets.UTF_8);
|
buf = string.substring(0, Short.MAX_VALUE).getBytes(StandardCharsets.UTF_8);
|
||||||
writeVarInt(Short.MAX_VALUE);
|
writeVarInt(Short.MAX_VALUE);
|
||||||
} else {
|
} else {
|
||||||
buf = string.getBytes(StandardCharsets.UTF_8);
|
buf = string.getBytes(StandardCharsets.UTF_8);
|
||||||
writeVarInt(string.length());
|
writeVarInt(length);
|
||||||
}
|
}
|
||||||
|
|
||||||
writeBytes(buf);
|
writeBytes(buf);
|
||||||
@@ -55,7 +57,14 @@ public abstract class NetOutputStream extends OutputStream {
|
|||||||
writeByte(value);
|
writeByte(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// VarLong
|
public void writeVarLong(long value) {
|
||||||
|
while ((value & -128L) != 0L) {
|
||||||
|
writeByte((int)(value & 127L) | 128);
|
||||||
|
value >>>= 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeByte((int)value);
|
||||||
|
}
|
||||||
|
|
||||||
// Entity Metadata
|
// Entity Metadata
|
||||||
|
|
||||||
@@ -67,7 +76,10 @@ public abstract class NetOutputStream extends OutputStream {
|
|||||||
|
|
||||||
// Angle [1]
|
// Angle [1]
|
||||||
|
|
||||||
// UUID [16]
|
public void writeUUID(UUID uuid) {
|
||||||
|
writeLong(uuid.getMostSignificantBits());
|
||||||
|
writeLong(uuid.getLeastSignificantBits());
|
||||||
|
}
|
||||||
|
|
||||||
public void writeBytes(byte[] buffer) {
|
public void writeBytes(byte[] buffer) {
|
||||||
writeBytes(buffer, 0, buffer.length);
|
writeBytes(buffer, 0, buffer.length);
|
||||||
@@ -88,4 +100,12 @@ public abstract class NetOutputStream extends OutputStream {
|
|||||||
public abstract void writeBytes(byte[] buffer, int offset, int lengtn);
|
public abstract void writeBytes(byte[] buffer, int offset, int lengtn);
|
||||||
|
|
||||||
public abstract void writeShort(int value);
|
public abstract void writeShort(int value);
|
||||||
|
|
||||||
|
public abstract void writeInt(int value);
|
||||||
|
|
||||||
|
public abstract void writeLong(long value);
|
||||||
|
|
||||||
|
public abstract void writeFloat(float value);
|
||||||
|
|
||||||
|
public abstract void writeDouble(double value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
package mc.protocol.io.coder;
|
|
||||||
|
|
||||||
import mc.protocol.io.NetOutputStream;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
|
|
||||||
class ByteArrayNetOutputStream extends NetOutputStream {
|
|
||||||
|
|
||||||
private ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeByte(int value) {
|
|
||||||
baos.write(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeBytes(byte[] buffer, int offset, int lengtn) {
|
|
||||||
baos.write(buffer, offset, lengtn);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeShort(int value) {
|
|
||||||
baos.write((value >>> 8) & 0xFF);
|
|
||||||
baos.write(value & 0xFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
int size() {
|
|
||||||
return baos.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] toByteArray() {
|
|
||||||
return baos.toByteArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import mc.protocol.Packet;
|
import mc.protocol.Packet;
|
||||||
import mc.protocol.PacketDirection;
|
import mc.protocol.PacketDirection;
|
||||||
import mc.protocol.State;
|
import mc.protocol.State;
|
||||||
|
import mc.protocol.io.ByteArrayNetOutputStream;
|
||||||
import mc.protocol.io.NetOutputStream;
|
import mc.protocol.io.NetOutputStream;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|||||||
63
src/test/java/mc/protocol/io/ByteArrayNetInputStream.java
Normal file
63
src/test/java/mc/protocol/io/ByteArrayNetInputStream.java
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package mc.protocol.io;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
class ByteArrayNetInputStream extends NetInputStream {
|
||||||
|
|
||||||
|
private final ByteBuffer byteBuffer;
|
||||||
|
private int index = 0;
|
||||||
|
|
||||||
|
public ByteArrayNetInputStream(byte[] buffer) {
|
||||||
|
byteBuffer = ByteBuffer.wrap(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void markReadIndex() {
|
||||||
|
index = byteBuffer.position();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resetReadIndex() {
|
||||||
|
byteBuffer.position(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int readableBytes() {
|
||||||
|
return byteBuffer.limit(); //TODO нужно простестировать
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte readByte() {
|
||||||
|
return byteBuffer.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int readBytes(byte[] buffer, int offset, int lengtn) {
|
||||||
|
return readableBytes() - byteBuffer.get(buffer, offset, lengtn).remaining();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int readShort() {
|
||||||
|
return byteBuffer.getShort();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int readInt() {
|
||||||
|
return byteBuffer.getInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long readLong() {
|
||||||
|
return byteBuffer.getLong();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float readFloat() {
|
||||||
|
return byteBuffer.getFloat();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double readDouble() {
|
||||||
|
return byteBuffer.getDouble();
|
||||||
|
}
|
||||||
|
}
|
||||||
300
src/test/java/mc/protocol/io/NetInputStreamTest.java
Normal file
300
src/test/java/mc/protocol/io/NetInputStreamTest.java
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
package mc.protocol.io;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class NetInputStreamTest {
|
||||||
|
|
||||||
|
private Random random;
|
||||||
|
private ByteArrayOutputStream baos;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
random = new Random(System.currentTimeMillis());
|
||||||
|
baos = new ByteArrayOutputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("paramsReadBoolean")
|
||||||
|
void readBoolean(byte sourceByte, boolean expectedValue) {
|
||||||
|
baos.write(sourceByte);
|
||||||
|
|
||||||
|
NetInputStream netInputStream = createNetInputStream(baos.toByteArray());
|
||||||
|
|
||||||
|
assertEquals(expectedValue, netInputStream.readBoolean());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void readByte() {
|
||||||
|
byte b = Integer.valueOf(random.nextInt()).byteValue();
|
||||||
|
baos.write(b);
|
||||||
|
|
||||||
|
assertEquals(b, createNetInputStream(baos.toByteArray()).readByte());
|
||||||
|
assertEquals(b, createNetInputStream(baos.toByteArray()).read());
|
||||||
|
|
||||||
|
baos.reset();
|
||||||
|
baos.write(128);
|
||||||
|
assertEquals(-128, createNetInputStream(baos.toByteArray()).readByte());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void readUnsignedByte() {
|
||||||
|
int value = 128;
|
||||||
|
baos.write(value);
|
||||||
|
|
||||||
|
assertEquals(value, createNetInputStream(baos.toByteArray()).readUnsignedByte());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void readShort() throws IOException {
|
||||||
|
int value = Integer.valueOf(random.nextInt()).shortValue();
|
||||||
|
createDataOutputStream().writeShort(value);
|
||||||
|
|
||||||
|
assertEquals(value, createNetInputStream(baos.toByteArray()).readShort());
|
||||||
|
|
||||||
|
baos.reset();
|
||||||
|
createDataOutputStream().writeShort(32768);
|
||||||
|
assertEquals(-32768, createNetInputStream(baos.toByteArray()).readShort());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void readUnsignedShort() throws IOException {
|
||||||
|
int value = 32768;
|
||||||
|
createDataOutputStream().writeShort(value);
|
||||||
|
|
||||||
|
assertEquals(value, createNetInputStream(baos.toByteArray()).readUnsignedShort());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("paramsReadUnsignedByte")
|
||||||
|
void readUnsignedByte(byte sourceByte, int expectedValue) {
|
||||||
|
baos.write(sourceByte);
|
||||||
|
|
||||||
|
assertEquals(expectedValue, createNetInputStream(baos.toByteArray()).readUnsignedByte());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("paramsReadString")
|
||||||
|
void readString(String string) throws IOException {
|
||||||
|
final byte[] strBytes = string.getBytes(StandardCharsets.UTF_8);
|
||||||
|
final byte[] bytes = new byte[strBytes.length + 1];
|
||||||
|
bytes[0] = (byte) string.codePoints().count(); // здесь считается, что размер поместится в один байт
|
||||||
|
System.arraycopy(strBytes, 0, bytes, 1, strBytes.length);
|
||||||
|
|
||||||
|
baos.write(bytes);
|
||||||
|
|
||||||
|
assertEquals(string, createNetInputStream(baos.toByteArray()).readString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void readString_overSize() throws IOException {
|
||||||
|
String string = "123";
|
||||||
|
final byte[] strBytes = string.getBytes(StandardCharsets.UTF_8);
|
||||||
|
final byte[] bytes = new byte[strBytes.length + 1];
|
||||||
|
final int length = string.length();
|
||||||
|
bytes[0] = (byte) (length + 1); // здесь считается, что размер поместится в один байт
|
||||||
|
System.arraycopy(strBytes, 0, bytes, 1, strBytes.length);
|
||||||
|
|
||||||
|
baos.write(bytes);
|
||||||
|
|
||||||
|
assertThrows(DecoderException.class, () -> createNetInputStream(baos.toByteArray()).readString(length));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void readString_lessZero() throws IOException {
|
||||||
|
String string = "123";
|
||||||
|
final byte[] strBytes = string.getBytes(StandardCharsets.UTF_8);
|
||||||
|
final byte[] bytes = new byte[strBytes.length + 5];
|
||||||
|
bytes[0] = (byte) 0xFF;
|
||||||
|
bytes[1] = (byte) 0xFF;
|
||||||
|
bytes[2] = (byte) 0xFF;
|
||||||
|
bytes[3] = (byte) 0xFF;
|
||||||
|
bytes[4] = (byte) 0x0F;
|
||||||
|
System.arraycopy(strBytes, 0, bytes, 5, strBytes.length);
|
||||||
|
|
||||||
|
baos.write(bytes);
|
||||||
|
|
||||||
|
assertThrows(DecoderException.class, () -> createNetInputStream(baos.toByteArray()).readString(-1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("paramsReadVarInt")
|
||||||
|
void readVarInt(byte[] sourceBytes, int expectedValue) throws IOException {
|
||||||
|
baos.write(sourceBytes);
|
||||||
|
|
||||||
|
assertEquals(expectedValue, createNetInputStream(baos.toByteArray()).readVarInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void readVarInt_tooBig() throws IOException {
|
||||||
|
baos.write(new byte[]{ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x0F });
|
||||||
|
|
||||||
|
assertEquals(-1, createNetInputStream(baos.toByteArray()).readVarInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource({"paramsReadVarInt", "paramsReadVarLong"})
|
||||||
|
void readVarLong(byte[] sourceBytes, long expectedValue) throws IOException {
|
||||||
|
baos.write(sourceBytes);
|
||||||
|
|
||||||
|
assertEquals(expectedValue, createNetInputStream(baos.toByteArray()).readVarLong());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void readVarLong_tooBig() throws IOException {
|
||||||
|
baos.write(new byte[]{ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
|
||||||
|
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
|
||||||
|
(byte) 0xFF, (byte) 0x0F });
|
||||||
|
|
||||||
|
assertEquals(-1, createNetInputStream(baos.toByteArray()).readVarLong());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void readUUID() throws IOException {
|
||||||
|
final UUID uuid = UUID.randomUUID();
|
||||||
|
final long mostSignificantBits = uuid.getMostSignificantBits();
|
||||||
|
final long leastSignificantBits = uuid.getLeastSignificantBits();
|
||||||
|
|
||||||
|
baos.write(new byte[]{
|
||||||
|
(byte) ((mostSignificantBits >>> 56) & 0xFF),
|
||||||
|
(byte) ((mostSignificantBits >>> 48) & 0xFF),
|
||||||
|
(byte) ((mostSignificantBits >>> 40) & 0xFF),
|
||||||
|
(byte) ((mostSignificantBits >>> 32) & 0xFF),
|
||||||
|
(byte) ((mostSignificantBits >>> 24) & 0xFF),
|
||||||
|
(byte) ((mostSignificantBits >>> 16) & 0xFF),
|
||||||
|
(byte) ((mostSignificantBits >>> 8) & 0xFF),
|
||||||
|
(byte) (mostSignificantBits & 0xFF)
|
||||||
|
});
|
||||||
|
baos.write(new byte[]{
|
||||||
|
(byte) ((leastSignificantBits >>> 56) & 0xFF),
|
||||||
|
(byte) ((leastSignificantBits >>> 48) & 0xFF),
|
||||||
|
(byte) ((leastSignificantBits >>> 40) & 0xFF),
|
||||||
|
(byte) ((leastSignificantBits >>> 32) & 0xFF),
|
||||||
|
(byte) ((leastSignificantBits >>> 24) & 0xFF),
|
||||||
|
(byte) ((leastSignificantBits >>> 16) & 0xFF),
|
||||||
|
(byte) ((leastSignificantBits >>> 8) & 0xFF),
|
||||||
|
(byte) (leastSignificantBits & 0xFF)
|
||||||
|
});
|
||||||
|
|
||||||
|
assertEquals(uuid, createNetInputStream(baos.toByteArray()).readUUID());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void readBytes() throws IOException {
|
||||||
|
byte[] bytes = new byte[128];
|
||||||
|
random.nextBytes(bytes);
|
||||||
|
baos.write(bytes);
|
||||||
|
|
||||||
|
byte[] actualBytes = new byte[128];
|
||||||
|
createNetInputStream(baos.toByteArray()).readBytes(actualBytes);
|
||||||
|
|
||||||
|
assertArrayEquals(bytes, actualBytes);
|
||||||
|
|
||||||
|
actualBytes = new byte[128];
|
||||||
|
int read = createNetInputStream(baos.toByteArray()).read(actualBytes);
|
||||||
|
|
||||||
|
assertArrayEquals(bytes, actualBytes);
|
||||||
|
assertEquals(128, read);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void read_offset() throws IOException {
|
||||||
|
byte[] bytes = new byte[128];
|
||||||
|
random.nextBytes(bytes);
|
||||||
|
baos.write(bytes);
|
||||||
|
|
||||||
|
byte[] actualBytes = new byte[128];
|
||||||
|
int read = createNetInputStream(baos.toByteArray()).read(actualBytes, 3, 11);
|
||||||
|
assertEquals(11, read);
|
||||||
|
|
||||||
|
byte[] buff1 = new byte[11];
|
||||||
|
System.arraycopy(bytes, 0, buff1, 0, 11);
|
||||||
|
byte[] buff2 = new byte[11];
|
||||||
|
System.arraycopy(actualBytes, 3, buff2, 0, 11);
|
||||||
|
|
||||||
|
assertArrayEquals(buff1, buff2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NetInputStream createNetInputStream(byte[] buffer) {
|
||||||
|
return new ByteArrayNetInputStream(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DataOutputStream createDataOutputStream() {
|
||||||
|
return new DataOutputStream(baos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> paramsReadBoolean() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of((byte) 0x00, false),
|
||||||
|
Arguments.of((byte) 0x01, true)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> paramsReadUnsignedByte() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of((byte) 30, 30),
|
||||||
|
Arguments.of((byte) (0xFF & 130), 130)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> paramsReadString() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(""),
|
||||||
|
Arguments.of("Latin"),
|
||||||
|
Arguments.of("Кириллица"),
|
||||||
|
Arguments.of("العربية"),
|
||||||
|
Arguments.of("ﬦﬣﬡ"), // Алфавитные формы представления
|
||||||
|
Arguments.of("\uD800\uDD07") // Эгейские цифры, [один]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> paramsReadVarInt() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(new byte[]{ 0x78 }, 120),
|
||||||
|
Arguments.of(new byte[]{ (byte) 0xE0, 0x5D }, 12000),
|
||||||
|
Arguments.of(new byte[]{ (byte) 0xC0, (byte) 0xA9, 0x07 }, 120000),
|
||||||
|
Arguments.of(new byte[]{ (byte) 0x80, (byte) 0x9C, (byte) 0x9C, (byte) 0x39 }, 120_000_000),
|
||||||
|
Arguments.of(new byte[]{ (byte) 0x80, (byte) 0x98, (byte) 0x9A, (byte) 0xBC, 0x04 }, 1_200_000_000)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> paramsReadVarLong() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(
|
||||||
|
new byte[]{ (byte) 0x80, (byte) 0xF0, (byte) 0x85, (byte) 0xDA, 0x2C },
|
||||||
|
12_000_000_000L),
|
||||||
|
Arguments.of(
|
||||||
|
new byte[]{ (byte) 0x80, (byte) 0xE0, (byte) 0xBA, (byte) 0x84, (byte) 0xBF, 0x03 },
|
||||||
|
120_000_000_000L),
|
||||||
|
Arguments.of(
|
||||||
|
new byte[]{ (byte) 0x80, (byte) 0x80, (byte) 0xF3, (byte) 0xBD, (byte) 0x9F, (byte) 0xDD,
|
||||||
|
0x02 },
|
||||||
|
12_000_000_000_000L),
|
||||||
|
Arguments.of(
|
||||||
|
new byte[]{ (byte) 0x80, (byte) 0x80, (byte) 0xEC, (byte) 0xAD, (byte) 0xCC, (byte) 0xEC,
|
||||||
|
(byte) 0x90, 0x02},
|
||||||
|
1_200_000_000_000_000L),
|
||||||
|
Arguments.of(
|
||||||
|
new byte[]{ (byte) 0x80, (byte) 0x80, (byte) 0xB0, (byte) 0xE8, (byte) 0xD3, (byte) 0xEB,
|
||||||
|
(byte) 0x94, (byte) 0xD5, 0x01 },
|
||||||
|
120_000_000_000_000_000L),
|
||||||
|
Arguments.of(
|
||||||
|
new byte[]{ (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80,
|
||||||
|
(byte) 0x80, (byte) 0x80, (byte) 0x80, 0x01 },
|
||||||
|
Long.MIN_VALUE)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
230
src/test/java/mc/protocol/io/NetOutputStreamTest.java
Normal file
230
src/test/java/mc/protocol/io/NetOutputStreamTest.java
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
package mc.protocol.io;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
class NetOutputStreamTest {
|
||||||
|
|
||||||
|
private static Random random;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void setUp() {
|
||||||
|
random = new Random(System.currentTimeMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("paramsWriteBoolean")
|
||||||
|
void writeBoolean(boolean sourceValue, byte expectedByte) {
|
||||||
|
NetOutputStream netOutputStream = createNetOutputStream();
|
||||||
|
netOutputStream.writeBoolean(sourceValue);
|
||||||
|
|
||||||
|
assertEquals(expectedByte, toByteArray(netOutputStream)[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("paramsWriteByte")
|
||||||
|
void writeByte(byte sourceValue, byte expectedByte) throws IOException {
|
||||||
|
NetOutputStream netOutputStream = createNetOutputStream();
|
||||||
|
netOutputStream.writeByte(sourceValue);
|
||||||
|
|
||||||
|
assertEquals(expectedByte, toByteArray(netOutputStream)[0]);
|
||||||
|
|
||||||
|
netOutputStream = createNetOutputStream();
|
||||||
|
netOutputStream.write(sourceValue);
|
||||||
|
|
||||||
|
assertEquals(expectedByte, toByteArray(netOutputStream)[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("paramsWriteString")
|
||||||
|
void writeString(String string) {
|
||||||
|
NetOutputStream netOutputStream = createNetOutputStream();
|
||||||
|
netOutputStream.writeString(string);
|
||||||
|
|
||||||
|
byte[] bytes = toByteArray(netOutputStream);
|
||||||
|
int length = bytes[0];
|
||||||
|
assertEquals(string.codePoints().count(), length);
|
||||||
|
|
||||||
|
byte[] dataBytes = new byte[bytes.length - 1];
|
||||||
|
System.arraycopy(bytes, 1, dataBytes, 0, dataBytes.length);
|
||||||
|
assertEquals(string, new String(dataBytes, StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void writeString_overSize() {
|
||||||
|
String overSizeString = RandomStringUtils.randomAscii(Short.MAX_VALUE + Short.MAX_VALUE);
|
||||||
|
NetOutputStream netOutputStream = createNetOutputStream();
|
||||||
|
netOutputStream.writeString(overSizeString);
|
||||||
|
|
||||||
|
NetInputStream netInputStream = new ByteArrayNetInputStream(toByteArray(netOutputStream));
|
||||||
|
String actualString = netInputStream.readString();
|
||||||
|
|
||||||
|
String expectedString = overSizeString.substring(0, Short.MAX_VALUE);
|
||||||
|
|
||||||
|
assertEquals(expectedString, actualString);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("paramsWriteVarInt")
|
||||||
|
void writeVarInt(int sourceValue, byte[] expectedBytes) {
|
||||||
|
NetOutputStream netOutputStream = createNetOutputStream();
|
||||||
|
netOutputStream.writeVarInt(sourceValue);
|
||||||
|
|
||||||
|
assertArrayEquals(expectedBytes, toByteArray(netOutputStream));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource({"paramsWriteVarInt", "paramsWriteVarLong"})
|
||||||
|
void writeVarLong(long sourceValue, byte[] expectedBytes) {
|
||||||
|
NetOutputStream netOutputStream = createNetOutputStream();
|
||||||
|
netOutputStream.writeVarLong(sourceValue);
|
||||||
|
|
||||||
|
assertArrayEquals(expectedBytes, toByteArray(netOutputStream));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void writeUUID() {
|
||||||
|
final UUID uuid = UUID.randomUUID();
|
||||||
|
|
||||||
|
NetOutputStream netOutputStream = createNetOutputStream();
|
||||||
|
netOutputStream.writeUUID(uuid);
|
||||||
|
|
||||||
|
final long mostSignificantBits = uuid.getMostSignificantBits();
|
||||||
|
final long leastSignificantBits = uuid.getLeastSignificantBits();
|
||||||
|
|
||||||
|
assertArrayEquals(new byte[]{ (byte) ((mostSignificantBits >>> 56) & 0xFF),
|
||||||
|
(byte) ((mostSignificantBits >>> 48) & 0xFF),
|
||||||
|
(byte) ((mostSignificantBits >>> 40) & 0xFF),
|
||||||
|
(byte) ((mostSignificantBits >>> 32) & 0xFF),
|
||||||
|
(byte) ((mostSignificantBits >>> 24) & 0xFF),
|
||||||
|
(byte) ((mostSignificantBits >>> 16) & 0xFF),
|
||||||
|
(byte) ((mostSignificantBits >>> 8) & 0xFF),
|
||||||
|
(byte) (mostSignificantBits & 0xFF),
|
||||||
|
|
||||||
|
(byte) ((leastSignificantBits >>> 56) & 0xFF),
|
||||||
|
(byte) ((leastSignificantBits >>> 48) & 0xFF),
|
||||||
|
(byte) ((leastSignificantBits >>> 40) & 0xFF),
|
||||||
|
(byte) ((leastSignificantBits >>> 32) & 0xFF),
|
||||||
|
(byte) ((leastSignificantBits >>> 24) & 0xFF),
|
||||||
|
(byte) ((leastSignificantBits >>> 16) & 0xFF),
|
||||||
|
(byte) ((leastSignificantBits >>> 8) & 0xFF),
|
||||||
|
(byte) (leastSignificantBits & 0xFF) },
|
||||||
|
toByteArray(netOutputStream));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void writeBytes() throws IOException {
|
||||||
|
byte[] bytes = new byte[128];
|
||||||
|
random.nextBytes(bytes);
|
||||||
|
NetOutputStream netOutputStream = createNetOutputStream();
|
||||||
|
netOutputStream.writeBytes(bytes);
|
||||||
|
|
||||||
|
assertArrayEquals(bytes, toByteArray(netOutputStream));
|
||||||
|
|
||||||
|
netOutputStream = createNetOutputStream();
|
||||||
|
netOutputStream.write(bytes);
|
||||||
|
|
||||||
|
assertArrayEquals(bytes, toByteArray(netOutputStream));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void write_offset() throws IOException {
|
||||||
|
byte[] bytes = new byte[128];
|
||||||
|
random.nextBytes(bytes);
|
||||||
|
NetOutputStream netOutputStream = createNetOutputStream();
|
||||||
|
netOutputStream.write(bytes, 3, 11);
|
||||||
|
|
||||||
|
byte[] actualBytes = new byte[11];
|
||||||
|
System.arraycopy(toByteArray(netOutputStream), 0, actualBytes, 0, 11);
|
||||||
|
|
||||||
|
byte[] expectedBytes = new byte[11];
|
||||||
|
System.arraycopy(bytes, 3, expectedBytes, 0, 11);
|
||||||
|
|
||||||
|
assertArrayEquals(expectedBytes, actualBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NetOutputStream createNetOutputStream() {
|
||||||
|
return new ByteArrayNetOutputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] toByteArray(NetOutputStream netOutputStream) {
|
||||||
|
return ((ByteArrayNetOutputStream) netOutputStream).toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> paramsWriteBoolean() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(false, (byte) 0x00),
|
||||||
|
Arguments.of(true, (byte) 0x01)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> paramsWriteByte() {
|
||||||
|
byte b = Integer.valueOf(random.nextInt()).byteValue();
|
||||||
|
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(b, b),
|
||||||
|
Arguments.of((byte) 128, (byte) -128)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> paramsWriteString() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(""),
|
||||||
|
Arguments.of("Latin"),
|
||||||
|
Arguments.of("Кириллица"),
|
||||||
|
Arguments.of("العربية"),
|
||||||
|
Arguments.of("ﬦﬣﬡ"), // Алфавитные формы представления
|
||||||
|
Arguments.of("\uD800\uDD07") // Эгейские цифры, [один]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> paramsWriteVarInt() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(120, new byte[]{ 0x78 }),
|
||||||
|
Arguments.of(12000, new byte[]{ (byte) 0xE0, 0x5D }),
|
||||||
|
Arguments.of(120000, new byte[]{ (byte) 0xC0, (byte) 0xA9, 0x07 }),
|
||||||
|
Arguments.of(120000000, new byte[]{ (byte) 0x80, (byte) 0x9C, (byte) 0x9C, (byte) 0x39 }),
|
||||||
|
Arguments.of(1200000000, new byte[]{ (byte) 0x80, (byte) 0x98, (byte) 0x9A, (byte) 0xBC, 0x04 })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> paramsWriteVarLong() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(
|
||||||
|
12_000_000_000L,
|
||||||
|
new byte[]{ (byte) 0x80, (byte) 0xF0, (byte) 0x85, (byte) 0xDA, 0x2C }),
|
||||||
|
Arguments.of(
|
||||||
|
120_000_000_000L,
|
||||||
|
new byte[]{ (byte) 0x80, (byte) 0xE0, (byte) 0xBA, (byte) 0x84, (byte) 0xBF, 0x03 }),
|
||||||
|
Arguments.of(
|
||||||
|
12_000_000_000_000L,
|
||||||
|
new byte[]{ (byte) 0x80, (byte) 0x80, (byte) 0xF3, (byte) 0xBD, (byte) 0x9F, (byte) 0xDD,
|
||||||
|
0x02 }),
|
||||||
|
Arguments.of(
|
||||||
|
1_200_000_000_000_000L,
|
||||||
|
new byte[]{ (byte) 0x80, (byte) 0x80, (byte) 0xEC, (byte) 0xAD, (byte) 0xCC, (byte) 0xEC,
|
||||||
|
(byte) 0x90, 0x02}),
|
||||||
|
Arguments.of(
|
||||||
|
120_000_000_000_000_000L,
|
||||||
|
new byte[]{ (byte) 0x80, (byte) 0x80, (byte) 0xB0, (byte) 0xE8, (byte) 0xD3, (byte) 0xEB,
|
||||||
|
(byte) 0x94, (byte) 0xD5, 0x01 }),
|
||||||
|
Arguments.of(
|
||||||
|
Long.MIN_VALUE,
|
||||||
|
new byte[]{ (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80,
|
||||||
|
(byte) 0x80, (byte) 0x80, (byte) 0x80, 0x01 })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user