отображение информации о сервере
This commit is contained in:
15
build.gradle
15
build.gradle
@@ -43,6 +43,19 @@ dependencies {
|
|||||||
implementation 'io.projectreactor:reactor-core'
|
implementation 'io.projectreactor:reactor-core'
|
||||||
|
|
||||||
implementation 'io.netty:netty-all:4.1.22.Final'
|
implementation 'io.netty:netty-all:4.1.22.Final'
|
||||||
|
implementation libs.guava
|
||||||
|
|
||||||
|
testImplementation libs.junit5.api
|
||||||
|
testImplementation libs.junit5.params
|
||||||
|
testRuntimeOnly libs.junit5.engine
|
||||||
|
|
||||||
|
testImplementation libs.lang3
|
||||||
}
|
}
|
||||||
|
|
||||||
application.mainClassName = 'mc.server.Main'
|
application {
|
||||||
|
mainClassName = 'mc.server.Main'
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,8 +6,12 @@ public class LibsExtention {
|
|||||||
|
|
||||||
public final String lombok = "org.projectlombok:lombok:1.18.12";
|
public final String lombok = "org.projectlombok:lombok:1.18.12";
|
||||||
public final String annotations = "com.google.code.findbugs:jsr305:3.0.2";
|
public final String annotations = "com.google.code.findbugs:jsr305:3.0.2";
|
||||||
|
public final String guava = "com.google.guava:guava:30.1-jre";
|
||||||
|
public final String lang3 = "org.apache.commons:commons-lang3:3.11";
|
||||||
|
|
||||||
public final LoggerLibs logger = new LoggerLibs();
|
public final LoggerLibs logger = new LoggerLibs();
|
||||||
public final Dagger2Libs dagger2 = new Dagger2Libs();
|
public final Dagger2Libs dagger2 = new Dagger2Libs();
|
||||||
|
public final Junit5Libs junit5 = new Junit5Libs();
|
||||||
|
|
||||||
public static final class LoggerLibs {
|
public static final class LoggerLibs {
|
||||||
private final String slf4j_version = "1.7.30";
|
private final String slf4j_version = "1.7.30";
|
||||||
@@ -17,7 +21,6 @@ public class LibsExtention {
|
|||||||
"org.slf4j:slf4j-api:" + slf4j_version,
|
"org.slf4j:slf4j-api:" + slf4j_version,
|
||||||
"org.slf4j:jcl-over-slf4j:" + slf4j_version
|
"org.slf4j:jcl-over-slf4j:" + slf4j_version
|
||||||
);
|
);
|
||||||
public final String slf4j_simple = "org.slf4j:slf4j-simple:" + slf4j_version;
|
|
||||||
|
|
||||||
public final List<String> logback = List.of(
|
public final List<String> logback = List.of(
|
||||||
"ch.qos.logback:logback-core:" + logback_version,
|
"ch.qos.logback:logback-core:" + logback_version,
|
||||||
@@ -31,4 +34,13 @@ public class LibsExtention {
|
|||||||
public final String implementation = "com.google.dagger:dagger:" + dagger2_version;
|
public final String implementation = "com.google.dagger:dagger:" + dagger2_version;
|
||||||
public final String annotationProcessor = "com.google.dagger:dagger-compiler:" + dagger2_version;
|
public final String annotationProcessor = "com.google.dagger:dagger-compiler:" + dagger2_version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final class Junit5Libs {
|
||||||
|
private final String junit_version = "5.5.2";
|
||||||
|
|
||||||
|
public final String api = "org.junit.jupiter:junit-jupiter-api:" + junit_version;
|
||||||
|
/** runtimeOnly */
|
||||||
|
public final String engine = "org.junit.jupiter:junit-jupiter-engine:" + junit_version;
|
||||||
|
public final String params = "org.junit.jupiter:junit-jupiter-params:" + junit_version;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
src/main/java/mc/protocol/NetworkAttributes.java
Normal file
10
src/main/java/mc/protocol/NetworkAttributes.java
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package mc.protocol;
|
||||||
|
|
||||||
|
import io.netty.util.AttributeKey;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
public class NetworkAttributes {
|
||||||
|
|
||||||
|
public static final AttributeKey<State> STATE = AttributeKey.newInstance("STATE");
|
||||||
|
}
|
||||||
69
src/main/java/mc/protocol/State.java
Normal file
69
src/main/java/mc/protocol/State.java
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package mc.protocol;
|
||||||
|
|
||||||
|
import com.google.common.collect.BiMap;
|
||||||
|
import com.google.common.collect.ImmutableBiMap;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import mc.protocol.packets.Packet;
|
||||||
|
import mc.protocol.packets.PacketDirection;
|
||||||
|
import mc.protocol.packets.client.HandshakePacket;
|
||||||
|
import mc.protocol.packets.client.StatusServerRequest;
|
||||||
|
import mc.protocol.packets.server.StatusServerResponse;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public enum State {
|
||||||
|
|
||||||
|
HANDSHAKING(-1,
|
||||||
|
// server bound
|
||||||
|
ImmutableBiMap.of(0x00, HandshakePacket.class)
|
||||||
|
),
|
||||||
|
STATUS(1,
|
||||||
|
// server bound
|
||||||
|
ImmutableBiMap.of(0x00, StatusServerRequest.class),
|
||||||
|
// client bound
|
||||||
|
ImmutableBiMap.of(0x00, StatusServerResponse.class)
|
||||||
|
);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static State getById(int id) {
|
||||||
|
for (State state : State.values()) {
|
||||||
|
if (state.id == id) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final int id;
|
||||||
|
|
||||||
|
private final BiMap<Integer, Class<? extends Packet>> serverBoundPackets;
|
||||||
|
private final BiMap<Integer, Class<? extends Packet>> clientBoundPackets;
|
||||||
|
|
||||||
|
State(int id, BiMap<Integer, Class<? extends Packet>> serverBoundPackets) {
|
||||||
|
this.id = id;
|
||||||
|
this.serverBoundPackets = serverBoundPackets;
|
||||||
|
this.clientBoundPackets = ImmutableBiMap.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Class<? extends Packet> getPacketById(PacketDirection direction, int id) {
|
||||||
|
if (PacketDirection.CLIENT_BOUND == direction) {
|
||||||
|
return clientBoundPackets == null ? null : clientBoundPackets.get(id);
|
||||||
|
} else {
|
||||||
|
return serverBoundPackets == null ? null : serverBoundPackets.get(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Integer getIdByPacket(PacketDirection direction, Class<? extends Packet> clazz) {
|
||||||
|
if (PacketDirection.CLIENT_BOUND == direction) {
|
||||||
|
return clientBoundPackets == null ? null : clientBoundPackets.inverse().get(clazz);
|
||||||
|
} else {
|
||||||
|
return serverBoundPackets == null ? null : serverBoundPackets.inverse().get(clazz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
src/main/java/mc/protocol/io/DecoderException.java
Normal file
8
src/main/java/mc/protocol/io/DecoderException.java
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package mc.protocol.io;
|
||||||
|
|
||||||
|
public class DecoderException extends RuntimeException {
|
||||||
|
|
||||||
|
public DecoderException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
183
src/main/java/mc/protocol/io/NetByteBuf.java
Normal file
183
src/main/java/mc/protocol/io/NetByteBuf.java
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
package mc.protocol.io;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.ToString;
|
||||||
|
import lombok.experimental.Delegate;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Компонент чтения и записи данных протокола.
|
||||||
|
*
|
||||||
|
* <p>Data types</p>
|
||||||
|
* <pre>
|
||||||
|
* | TYPE | SIZE (bytes) | ENCODING | NOTES |
|
||||||
|
* |----------------|-----------------------|-----------------------------------------------------|--------------------------------------------------------------------------|
|
||||||
|
* | Boolean | 1 | True или False | True = 0x01; False = 0x00 |
|
||||||
|
* | Byte | 1 | Число от -128 до 127 | 8-bit число со знаком |
|
||||||
|
* | Unsigned Byte | 1 | Число от 0 до 255 | 8-bit без знаковое число |
|
||||||
|
* | Short | 2 | Число от -32768 до 32767 | 16-bit число со знаком |
|
||||||
|
* | Unsigned Short | 2 | Число от -32768 до 32767 | 16-bit без знаковое число |
|
||||||
|
* | Int | 4 | Число от -2147483648 и 2147483647 | 32-bit число со знаком |
|
||||||
|
* | Long | 8 | Число от -9223372036854775808 и 9223372036854775807 | 64-bit число со знаком |
|
||||||
|
* | Float | 4 | 32-bit число одинарной точности (IEEE 754-2008) | [1] |
|
||||||
|
* | Double | 8 | 64-bit число одинарной точности (IEEE 754-2008) | [2] |
|
||||||
|
* | String (n) | >= 1 ; <= (n * 4) + 3 | Последовательность Unicode scalar values | В начале пишется длина строки в VarInt, после чего записываются символы. |
|
||||||
|
* | | | | Каждый символ может состоять максимум из 4 байт. [3] |
|
||||||
|
* | | | | Максимальная длина строки - 32767 (3 - это как раз размер VarInt для |
|
||||||
|
* | | | | этого числа). |
|
||||||
|
* | VarInt | >= 1 ; <= 5 | Число от -2147483648 и 2147483647 | 32-bit число с плавающей размерностью от 1 до 5 байт |
|
||||||
|
* | VarLong | >= 1 ; <= 10 | Число от -9223372036854775808 и 9223372036854775807 | 64-bit число с плавающей размерностью от 1 до 10 байт |
|
||||||
|
*
|
||||||
|
* [1] - <a href="https://en.wikipedia.org/wiki/Single-precision_floating-point_format">Single-precision floating-point format</a>
|
||||||
|
* [2] - <a href="https://en.wikipedia.org/wiki/Double-precision_floating-point_format">Double-precision floating-point format</a>
|
||||||
|
* [3] - <a href="http://unicode.org/glossary/#unicode_scalar_value">Unicode Scalar Value</a>
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=7368#Data_types">Data types</a>
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
@ToString
|
||||||
|
public class NetByteBuf extends ByteBuf {
|
||||||
|
|
||||||
|
@Delegate
|
||||||
|
private final ByteBuf byteBuf;
|
||||||
|
|
||||||
|
//region String
|
||||||
|
public String readString() {
|
||||||
|
return readString(Short.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("java:S131")
|
||||||
|
public String readString(int maxLength) {
|
||||||
|
int length = readVarInt();
|
||||||
|
|
||||||
|
if (length == 0) {
|
||||||
|
return "";
|
||||||
|
} else if (length > maxLength) {
|
||||||
|
throw new DecoderException("String length exceeds maximum length: " + length + " > " + maxLength);
|
||||||
|
} else if (length < 0) {
|
||||||
|
throw new DecoderException("String length less zero!");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] bytes = new byte[length * 4];
|
||||||
|
int readbleBytes = 0;
|
||||||
|
for (int i = 0; i < length && readableBytes() > 0; 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new String(bytes, 0, readbleBytes, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeString(String string) {
|
||||||
|
byte[] buf;
|
||||||
|
int length = (int) string.codePoints().count();
|
||||||
|
|
||||||
|
if (length > Short.MAX_VALUE) {
|
||||||
|
log.warn("String is too long: {} > {}", length, Short.MAX_VALUE);
|
||||||
|
buf = string.substring(0, Short.MAX_VALUE).getBytes(StandardCharsets.UTF_8);
|
||||||
|
writeVarInt(Short.MAX_VALUE);
|
||||||
|
} else {
|
||||||
|
buf = string.getBytes(StandardCharsets.UTF_8);
|
||||||
|
writeVarInt(length);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeBytes(buf);
|
||||||
|
}
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
//region VarInt
|
||||||
|
public int readVarInt() {
|
||||||
|
int numRead = 0;
|
||||||
|
int result = 0;
|
||||||
|
byte read;
|
||||||
|
do {
|
||||||
|
if ((numRead + 1) > 5) {
|
||||||
|
log.warn("VarInt is too big");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
read = readByte();
|
||||||
|
int value = (read & 0b01111111);
|
||||||
|
result |= (value << (7 * numRead));
|
||||||
|
|
||||||
|
numRead++;
|
||||||
|
} while ((read & 0b10000000) != 0);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeVarInt(int value) {
|
||||||
|
while ((value & -128) != 0) {
|
||||||
|
writeByte(value & 127 | 128);
|
||||||
|
value >>>= 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeByte(value);
|
||||||
|
}
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
//region VarLong
|
||||||
|
public long readVarLong() {
|
||||||
|
int numRead = 0;
|
||||||
|
long result = 0L;
|
||||||
|
byte read;
|
||||||
|
do {
|
||||||
|
if (numRead > 10) {
|
||||||
|
log.warn("VarLong is too big");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
read = readByte();
|
||||||
|
long value = (read & 0b01111111);
|
||||||
|
result |= (value << (7 * numRead));
|
||||||
|
|
||||||
|
numRead++;
|
||||||
|
} while ((read & 0b10000000) != 0);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeVarLong(long value) {
|
||||||
|
while ((value & -128L) != 0L) {
|
||||||
|
writeByte((int) (value & 127L) | 128);
|
||||||
|
value >>>= 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeByte((int) value);
|
||||||
|
}
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
//region UUID
|
||||||
|
public UUID readUUID() {
|
||||||
|
return new UUID(readLong(), readLong());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeUUID(UUID uuid) {
|
||||||
|
writeLong(uuid.getMostSignificantBits());
|
||||||
|
writeLong(uuid.getLeastSignificantBits());
|
||||||
|
}
|
||||||
|
//endregion
|
||||||
|
}
|
||||||
59
src/main/java/mc/protocol/io/codec/ProtocolDecoder.java
Normal file
59
src/main/java/mc/protocol/io/codec/ProtocolDecoder.java
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package mc.protocol.io.codec;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import mc.protocol.NetworkAttributes;
|
||||||
|
import mc.protocol.State;
|
||||||
|
import mc.protocol.io.NetByteBuf;
|
||||||
|
import mc.protocol.packets.Packet;
|
||||||
|
import mc.protocol.packets.PacketDirection;
|
||||||
|
import mc.protocol.packets.UnknownPacket;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ProtocolDecoder extends ByteToMessageDecoder {
|
||||||
|
|
||||||
|
private final boolean readUnknownPackets;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
ctx.channel().attr(NetworkAttributes.STATE).set(State.HANDSHAKING);
|
||||||
|
super.channelActive(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
ctx.channel().attr(NetworkAttributes.STATE).set(null);
|
||||||
|
super.channelInactive(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||||
|
State state = Objects.requireNonNull(ctx.channel().attr(NetworkAttributes.STATE).get());
|
||||||
|
NetByteBuf netByteBuf = new NetByteBuf(in);
|
||||||
|
|
||||||
|
int packetId = netByteBuf.readVarInt();
|
||||||
|
Class<? extends Packet> packetClass = state.getPacketById(PacketDirection.SERVER_BOUND, packetId);
|
||||||
|
if (packetClass == null) {
|
||||||
|
log.warn("Unkown packet: State {} ; Id {}", state, packetId);
|
||||||
|
|
||||||
|
if (readUnknownPackets) {
|
||||||
|
UnknownPacket unknownPacket = new UnknownPacket(state, packetId, netByteBuf.readableBytes());
|
||||||
|
unknownPacket.readSelf(netByteBuf);
|
||||||
|
out.add(unknownPacket);
|
||||||
|
} else {
|
||||||
|
netByteBuf.skipBytes(netByteBuf.readableBytes());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Packet packet = packetClass.getDeclaredConstructor().newInstance();
|
||||||
|
packet.readSelf(netByteBuf);
|
||||||
|
out.add(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/main/java/mc/protocol/io/codec/ProtocolEncoder.java
Normal file
30
src/main/java/mc/protocol/io/codec/ProtocolEncoder.java
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package mc.protocol.io.codec;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.handler.codec.MessageToByteEncoder;
|
||||||
|
import mc.protocol.NetworkAttributes;
|
||||||
|
import mc.protocol.State;
|
||||||
|
import mc.protocol.io.NetByteBuf;
|
||||||
|
import mc.protocol.packets.Packet;
|
||||||
|
import mc.protocol.packets.PacketDirection;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class ProtocolEncoder extends MessageToByteEncoder<Packet> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void encode(ChannelHandlerContext ctx, Packet packet, ByteBuf out) {
|
||||||
|
State state = ctx.channel().attr(NetworkAttributes.STATE).get();
|
||||||
|
int packetId = Objects.requireNonNull(state.getIdByPacket(PacketDirection.CLIENT_BOUND, packet.getClass()));
|
||||||
|
|
||||||
|
NetByteBuf buffer = new NetByteBuf(Unpooled.buffer());
|
||||||
|
buffer.writeVarInt(packetId);
|
||||||
|
packet.writeSelf(buffer);
|
||||||
|
|
||||||
|
NetByteBuf netByteBuf = new NetByteBuf(out);
|
||||||
|
netByteBuf.writeVarInt(buffer.readableBytes());
|
||||||
|
netByteBuf.writeBytes(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
40
src/main/java/mc/protocol/io/codec/ProtocolSplitter.java
Normal file
40
src/main/java/mc/protocol/io/codec/ProtocolSplitter.java
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package mc.protocol.io.codec;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||||
|
import mc.protocol.io.NetByteBuf;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ProtocolSplitter extends ByteToMessageDecoder {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
|
||||||
|
NetByteBuf netByteBuf = new NetByteBuf(in);
|
||||||
|
netByteBuf.markReaderIndex();
|
||||||
|
|
||||||
|
do {
|
||||||
|
byte[] sizePacketRaw = new byte[3];
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
sizePacketRaw[i] = netByteBuf.readByte();
|
||||||
|
|
||||||
|
if (sizePacketRaw[i] >= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int sizePacket = new NetByteBuf(Unpooled.wrappedBuffer(sizePacketRaw)).readVarInt();
|
||||||
|
|
||||||
|
if (netByteBuf.readableBytes() >= sizePacket) {
|
||||||
|
byte[] bytes = new byte[sizePacket];
|
||||||
|
netByteBuf.readBytes(bytes);
|
||||||
|
out.add(Unpooled.wrappedBuffer(bytes));
|
||||||
|
} else {
|
||||||
|
netByteBuf.resetReaderIndex();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while (netByteBuf.readableBytes() > 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/main/java/mc/protocol/packets/EmptyPacket.java
Normal file
16
src/main/java/mc/protocol/packets/EmptyPacket.java
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package mc.protocol.packets;
|
||||||
|
|
||||||
|
import mc.protocol.io.NetByteBuf;
|
||||||
|
|
||||||
|
public abstract class EmptyPacket implements Packet {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readSelf(NetByteBuf netByteBuf) {
|
||||||
|
// empty
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeSelf(NetByteBuf netByteBuf) {
|
||||||
|
// empty
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/main/java/mc/protocol/packets/Packet.java
Normal file
24
src/main/java/mc/protocol/packets/Packet.java
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package mc.protocol.packets;
|
||||||
|
|
||||||
|
import mc.protocol.io.NetByteBuf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Пакет.
|
||||||
|
*
|
||||||
|
* <p>Структура пакета</p>
|
||||||
|
* <pre>
|
||||||
|
* | FIELD | TYPE | NOTES |
|
||||||
|
* |-------------|--------|-------------------------------------------|
|
||||||
|
* | SIZE | VarInt | = sizeOf(PACKET ID) + sizeOf(PACKET DATA) |
|
||||||
|
* | PACKET ID | VarInt | |
|
||||||
|
* | PACKET DATA | bytes | |
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=7368#Without_compression">Packet without compression</a>
|
||||||
|
*/
|
||||||
|
public interface Packet {
|
||||||
|
|
||||||
|
void readSelf(NetByteBuf netByteBuf);
|
||||||
|
|
||||||
|
void writeSelf(NetByteBuf netByteBuf);
|
||||||
|
}
|
||||||
6
src/main/java/mc/protocol/packets/PacketDirection.java
Normal file
6
src/main/java/mc/protocol/packets/PacketDirection.java
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package mc.protocol.packets;
|
||||||
|
|
||||||
|
public enum PacketDirection {
|
||||||
|
|
||||||
|
SERVER_BOUND, CLIENT_BOUND
|
||||||
|
}
|
||||||
27
src/main/java/mc/protocol/packets/UnknownPacket.java
Normal file
27
src/main/java/mc/protocol/packets/UnknownPacket.java
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package mc.protocol.packets;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.ToString;
|
||||||
|
import mc.protocol.State;
|
||||||
|
import mc.protocol.io.NetByteBuf;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ToString(exclude = "rawData")
|
||||||
|
public class UnknownPacket implements Packet {
|
||||||
|
|
||||||
|
private final State state;
|
||||||
|
private final int id;
|
||||||
|
private final int dataSize;
|
||||||
|
private byte[] rawData;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readSelf(NetByteBuf netByteBuf) {
|
||||||
|
rawData = new byte[dataSize];
|
||||||
|
netByteBuf.readBytes(rawData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeSelf(NetByteBuf netByteBuf) {
|
||||||
|
netByteBuf.writeBytes(rawData);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package mc.protocol.packets.client;
|
||||||
|
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.ToString;
|
||||||
|
import mc.protocol.State;
|
||||||
|
import mc.protocol.io.NetByteBuf;
|
||||||
|
import mc.protocol.packets.Packet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handshake packet.
|
||||||
|
*
|
||||||
|
* <p>Данный пакет заставляет сервер переключить текущий {@link State}</p>
|
||||||
|
*
|
||||||
|
* <p>Структура пакета</p>
|
||||||
|
* <pre>
|
||||||
|
* | FIELD | TYPE | NOTES |
|
||||||
|
* |------------------|----------------|----------------------------------------------|
|
||||||
|
* | Protocol version | VarInt | Версия протокола [1] |
|
||||||
|
* | Server address | Stirng | Hostname или IP |
|
||||||
|
* | Server port | Unsigned Short | Порт сервера |
|
||||||
|
* | Next stage | VarInt | ID State на который необходимо переключиться |
|
||||||
|
*
|
||||||
|
* [1] - <a href="https://wiki.vg/Protocol_version_numbers" target="_top">Protocol version numbers</a>
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=7368#Handshake" target="_top">Handshake</a>
|
||||||
|
* @see State
|
||||||
|
*/
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Getter
|
||||||
|
@EqualsAndHashCode
|
||||||
|
@ToString
|
||||||
|
public class HandshakePacket implements Packet {
|
||||||
|
|
||||||
|
private int protocolVersion;
|
||||||
|
private String host;
|
||||||
|
private int port;
|
||||||
|
private State nextState;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readSelf(NetByteBuf netByteBuf) {
|
||||||
|
protocolVersion = netByteBuf.readVarInt();
|
||||||
|
host = netByteBuf.readString(255);
|
||||||
|
port = netByteBuf.readUnsignedShort();
|
||||||
|
nextState = State.getById(netByteBuf.readVarInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeSelf(NetByteBuf netByteBuf) {
|
||||||
|
netByteBuf.writeVarInt(protocolVersion);
|
||||||
|
netByteBuf.writeString(host);
|
||||||
|
netByteBuf.writeShort(port);
|
||||||
|
netByteBuf.writeVarInt(nextState.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package mc.protocol.packets.client;
|
||||||
|
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.ToString;
|
||||||
|
import mc.protocol.packets.EmptyPacket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Status server packet, request.
|
||||||
|
*
|
||||||
|
* <p>Клиент запрашивает получение информации о сервере</p>
|
||||||
|
*/
|
||||||
|
@NoArgsConstructor
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
@ToString
|
||||||
|
public class StatusServerRequest extends EmptyPacket {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package mc.protocol.packets.server;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import mc.protocol.io.NetByteBuf;
|
||||||
|
import mc.protocol.packets.Packet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Status server packet, response.
|
||||||
|
*
|
||||||
|
* <p>Информация о сервере</p>
|
||||||
|
*
|
||||||
|
* <p>Структура пакета
|
||||||
|
* <pre>
|
||||||
|
* | FIELD | TYPE | NOTES |
|
||||||
|
* |---------------|--------|-----------------------------------------|
|
||||||
|
* | JSON Response | String | Информация о сервере в JSON формате [1] |
|
||||||
|
*
|
||||||
|
* [1] - <a href="https://wiki.vg/index.php?title=Server_List_Ping&oldid=7555#Response" target="_top">Server List Ping: Response</a>
|
||||||
|
* </pre></p>
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class StatusServerResponse implements Packet {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Информация о серере в формате JSON
|
||||||
|
*
|
||||||
|
* <p>Пример</p>
|
||||||
|
* <pre>
|
||||||
|
* {
|
||||||
|
* "version": {
|
||||||
|
* "name": "1.8.7",
|
||||||
|
* "protocol": 47
|
||||||
|
* },
|
||||||
|
* "players": {
|
||||||
|
* "max": 100,
|
||||||
|
* "online": 5,
|
||||||
|
* "sample": [
|
||||||
|
* {
|
||||||
|
* "name": "thinkofdeath",
|
||||||
|
* "id": "4566e69f-c907-48ee-8d71-d7ba5aa00d20"
|
||||||
|
* }
|
||||||
|
* ]
|
||||||
|
* },
|
||||||
|
* "description": {
|
||||||
|
* "text": "Hello world"
|
||||||
|
* },
|
||||||
|
* "favicon": "data:image/png;base64,<data>"
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
private String info;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readSelf(NetByteBuf netByteBuf) {
|
||||||
|
info = netByteBuf.readString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeSelf(NetByteBuf netByteBuf) {
|
||||||
|
netByteBuf.writeString(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,6 @@ public class Main {
|
|||||||
|
|
||||||
NetworkComponent networkComponent = DaggerNetworkComponent.create();
|
NetworkComponent networkComponent = DaggerNetworkComponent.create();
|
||||||
Server server = networkComponent.getServer();
|
Server server = networkComponent.getServer();
|
||||||
server.start("127.0.0.1", 25565);
|
server.bind("127.0.0.1", 25565);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,19 +6,26 @@ import io.netty.bootstrap.ServerBootstrap;
|
|||||||
import io.netty.channel.ChannelHandler;
|
import io.netty.channel.ChannelHandler;
|
||||||
import io.netty.channel.ChannelInitializer;
|
import io.netty.channel.ChannelInitializer;
|
||||||
import io.netty.channel.ChannelPipeline;
|
import io.netty.channel.ChannelPipeline;
|
||||||
import io.netty.channel.EventLoopGroup;
|
|
||||||
import io.netty.channel.nio.NioEventLoopGroup;
|
import io.netty.channel.nio.NioEventLoopGroup;
|
||||||
import io.netty.channel.socket.SocketChannel;
|
import io.netty.channel.socket.SocketChannel;
|
||||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||||
|
import io.netty.handler.logging.LogLevel;
|
||||||
import io.netty.handler.logging.LoggingHandler;
|
import io.netty.handler.logging.LoggingHandler;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import mc.protocol.io.codec.ProtocolDecoder;
|
||||||
|
import mc.protocol.io.codec.ProtocolEncoder;
|
||||||
|
import mc.protocol.io.codec.ProtocolSplitter;
|
||||||
import mc.server.network.Server;
|
import mc.server.network.Server;
|
||||||
|
import mc.server.network.netty.HandshakeHandler;
|
||||||
import mc.server.network.netty.NettyServer;
|
import mc.server.network.netty.NettyServer;
|
||||||
|
import mc.server.network.netty.StatusHandler;
|
||||||
|
|
||||||
import javax.inject.Named;
|
import javax.inject.Provider;
|
||||||
import java.util.Collections;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.Map;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
|
@Slf4j
|
||||||
public class NetworkModule {
|
public class NetworkModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -27,14 +34,10 @@ public class NetworkModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
ServerBootstrap provideServerBootstrap(
|
ServerBootstrap provideServerBootstrap(ChannelInitializer<SocketChannel> channelChannelInitializer) {
|
||||||
@Named("boss-group") EventLoopGroup bossGroup,
|
|
||||||
@Named("worker-group") EventLoopGroup workerGroup,
|
|
||||||
ChannelInitializer<SocketChannel> channelChannelInitializer
|
|
||||||
) {
|
|
||||||
ServerBootstrap bootstrap = new ServerBootstrap();
|
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||||
|
|
||||||
bootstrap.group(bossGroup, workerGroup)
|
bootstrap.group(new NioEventLoopGroup(1), new NioEventLoopGroup())
|
||||||
.channel(NioServerSocketChannel.class)
|
.channel(NioServerSocketChannel.class)
|
||||||
.childHandler(channelChannelInitializer);
|
.childHandler(channelChannelInitializer);
|
||||||
|
|
||||||
@@ -42,30 +45,31 @@ public class NetworkModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Named("boss-group")
|
ChannelInitializer<SocketChannel> provideChannelChannelInitializer(Provider<Map<String, ChannelHandler>> channelHandlerMapProvider) {
|
||||||
EventLoopGroup provideBossGroup() {
|
|
||||||
return new NioEventLoopGroup(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Named("worker-group")
|
|
||||||
EventLoopGroup provideWorkerGroup() {
|
|
||||||
return new NioEventLoopGroup();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
ChannelInitializer<SocketChannel> provideChannelChannelInitializer(List<ChannelHandler> channelHandlerList) {
|
|
||||||
return new ChannelInitializer<>() {
|
return new ChannelInitializer<>() {
|
||||||
@Override
|
@Override
|
||||||
protected void initChannel(SocketChannel socketChannel) {
|
protected void initChannel(SocketChannel socketChannel) {
|
||||||
final ChannelPipeline pipeline = socketChannel.pipeline();
|
ChannelPipeline pipeline = socketChannel.pipeline();
|
||||||
channelHandlerList.forEach(pipeline::addLast);
|
channelHandlerMapProvider.get().forEach(pipeline::addLast);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
List<ChannelHandler> provideChannelHandlerList() {
|
Map<String, ChannelHandler> provideChannelHandlerMap(Provider<StatusHandler> statusHandlerProvider) {
|
||||||
return Collections.singletonList(new LoggingHandler());
|
Map<String, ChannelHandler> map = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
map.put("logger", new LoggingHandler(LogLevel.DEBUG));
|
||||||
|
map.put("protocol_splitter", new ProtocolSplitter());
|
||||||
|
map.put("protocol_decoder", new ProtocolDecoder(true));
|
||||||
|
map.put("protocol_encoder", new ProtocolEncoder());
|
||||||
|
map.put("handshake_handler", new HandshakeHandler(statusHandlerProvider));
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
StatusHandler provideStatusHandler() {
|
||||||
|
return new StatusHandler();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ package mc.server.network;
|
|||||||
|
|
||||||
public interface Server {
|
public interface Server {
|
||||||
|
|
||||||
void start(String host, int port);
|
void bind(String host, int port);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package mc.server.network.netty;
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.SimpleChannelInboundHandler;
|
||||||
|
import mc.protocol.packets.Packet;
|
||||||
|
|
||||||
|
public abstract class AbstractPacketHandler<P extends Packet> extends SimpleChannelInboundHandler<Packet> {
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
protected void channelRead0(ChannelHandlerContext ctx, Packet msg) throws Exception {
|
||||||
|
channelRead1(ctx, (P) msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("java:S112")
|
||||||
|
protected abstract void channelRead1(ChannelHandlerContext ctx, P packet) throws Exception;
|
||||||
|
}
|
||||||
24
src/main/java/mc/server/network/netty/HandshakeHandler.java
Normal file
24
src/main/java/mc/server/network/netty/HandshakeHandler.java
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package mc.server.network.netty;
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import mc.protocol.NetworkAttributes;
|
||||||
|
import mc.protocol.packets.client.HandshakePacket;
|
||||||
|
|
||||||
|
import javax.inject.Provider;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class HandshakeHandler extends AbstractPacketHandler<HandshakePacket> {
|
||||||
|
|
||||||
|
private final Provider<StatusHandler> statusHandlerProvider;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void channelRead1(ChannelHandlerContext ctx, HandshakePacket packet) {
|
||||||
|
log.info("{}", packet);
|
||||||
|
|
||||||
|
ctx.channel().attr(NetworkAttributes.STATE).set(packet.getNextState());
|
||||||
|
ctx.pipeline().replace("handshake_handler", "status_handler", statusHandlerProvider.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,12 +12,11 @@ public class NettyServer implements Server {
|
|||||||
private final ServerBootstrap serverBootstrap;
|
private final ServerBootstrap serverBootstrap;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(String host, int port) {
|
public void bind(String host, int port) {
|
||||||
log.info("Network starting: {}:{}", host, port);
|
log.info("Network starting: {}:{}", host, port);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
serverBootstrap.bind(host, port)
|
serverBootstrap.bind(host, port).sync().channel().closeFuture().sync();
|
||||||
.sync().channel().closeFuture().sync();
|
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
if (log.isTraceEnabled()) {
|
if (log.isTraceEnabled()) {
|
||||||
log.trace("{}: {}", e.getClass().getSimpleName(), e.getMessage(), e);
|
log.trace("{}: {}", e.getClass().getSimpleName(), e.getMessage(), e);
|
||||||
|
|||||||
33
src/main/java/mc/server/network/netty/StatusHandler.java
Normal file
33
src/main/java/mc/server/network/netty/StatusHandler.java
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package mc.server.network.netty;
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import mc.protocol.packets.client.StatusServerRequest;
|
||||||
|
import mc.protocol.packets.server.StatusServerResponse;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class StatusHandler extends AbstractPacketHandler<StatusServerRequest> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void channelRead1(ChannelHandlerContext ctx, StatusServerRequest packet) {
|
||||||
|
log.info("{}", packet);
|
||||||
|
|
||||||
|
StatusServerResponse response = new StatusServerResponse();
|
||||||
|
response.setInfo("{\n" +
|
||||||
|
" \"version\": {\n" +
|
||||||
|
" \"name\": \"1.12.2\",\n" +
|
||||||
|
" \"protocol\": 340\n" +
|
||||||
|
" },\n" +
|
||||||
|
" \"players\": {\n" +
|
||||||
|
" \"max\": 0,\n" +
|
||||||
|
" \"online\": 0,\n" +
|
||||||
|
" \"sample\": []\n" +
|
||||||
|
" },\n" +
|
||||||
|
" \"description\": {\n" +
|
||||||
|
" \"text\": \"Hello world\"\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}");
|
||||||
|
|
||||||
|
ctx.channel().writeAndFlush(response).channel().disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,11 +4,15 @@
|
|||||||
|
|
||||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
<layout class="ch.qos.logback.classic.PatternLayout">
|
<layout class="ch.qos.logback.classic.PatternLayout">
|
||||||
<Pattern>%d{HH:mm:ss.SSS} %-5level [%t] [%logger{36}] -- %msg%n</Pattern>
|
<Pattern>%d{HH:mm:ss.SSS} %-5level [%35.35logger{34}] -- %msg%n</Pattern>
|
||||||
</layout>
|
</layout>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
<root level="info">
|
<root level="info">
|
||||||
<appender-ref ref="CONSOLE"/>
|
<appender-ref ref="CONSOLE"/>
|
||||||
</root>
|
</root>
|
||||||
|
|
||||||
|
<logger name="io.netty.handler.logging.LoggingHandler" level="debug" additivity="false">
|
||||||
|
<appender-ref ref="CONSOLE"/>
|
||||||
|
</logger>
|
||||||
</configuration>
|
</configuration>
|
||||||
307
src/test/java/mc/protocol/io/NetByteBufReadTest.java
Normal file
307
src/test/java/mc/protocol/io/NetByteBufReadTest.java
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
package mc.protocol.io;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
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.*;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||||
|
|
||||||
|
class NetByteBufReadTest {
|
||||||
|
|
||||||
|
private static 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);
|
||||||
|
|
||||||
|
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
||||||
|
|
||||||
|
assertEquals(expectedValue, netByteBuf.readBoolean());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void readByte() {
|
||||||
|
byte[] bytes = new byte[1];
|
||||||
|
random.nextBytes(bytes);
|
||||||
|
baos.write(bytes[0]);
|
||||||
|
|
||||||
|
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
||||||
|
|
||||||
|
assertEquals(bytes[0], netByteBuf.readByte());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("paramsReadUnsignedByte")
|
||||||
|
void readUnsignedByte(byte sourceByte, int expectedValue) {
|
||||||
|
baos.write(sourceByte);
|
||||||
|
|
||||||
|
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
||||||
|
|
||||||
|
assertEquals(expectedValue, netByteBuf.readUnsignedByte());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void readShort() throws IOException {
|
||||||
|
int value = Integer.valueOf(random.nextInt()).shortValue();
|
||||||
|
new DataOutputStream(baos).writeShort(value);
|
||||||
|
|
||||||
|
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
||||||
|
|
||||||
|
assertEquals(value, netByteBuf.readShort());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void readUnsignedShort() throws IOException {
|
||||||
|
int value = 32768;
|
||||||
|
new DataOutputStream(baos).writeShort(value);
|
||||||
|
|
||||||
|
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
||||||
|
|
||||||
|
assertEquals(value, netByteBuf.readUnsignedShort());
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
|
||||||
|
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
||||||
|
|
||||||
|
assertEquals(string, netByteBuf.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);
|
||||||
|
|
||||||
|
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
||||||
|
|
||||||
|
assertThrows(DecoderException.class, () -> netByteBuf.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);
|
||||||
|
|
||||||
|
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
||||||
|
|
||||||
|
assertThrows(DecoderException.class, () -> netByteBuf.readString(-1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("paramsReadVarInt")
|
||||||
|
void readVarInt(byte[] sourceBytes, int expectedValue) throws IOException {
|
||||||
|
baos.write(sourceBytes);
|
||||||
|
|
||||||
|
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
||||||
|
|
||||||
|
assertEquals(expectedValue, netByteBuf.readVarInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void readVarInt_tooBig() throws IOException {
|
||||||
|
baos.write(new byte[]{ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x0F });
|
||||||
|
|
||||||
|
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
||||||
|
|
||||||
|
assertEquals(-1, netByteBuf.readVarInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource({"paramsReadVarInt", "paramsReadVarLong"})
|
||||||
|
void readVarLong(byte[] sourceBytes, long expectedValue) throws IOException {
|
||||||
|
baos.write(sourceBytes);
|
||||||
|
|
||||||
|
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
||||||
|
|
||||||
|
assertEquals(expectedValue, netByteBuf.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 });
|
||||||
|
|
||||||
|
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
||||||
|
|
||||||
|
assertEquals(-1, netByteBuf.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)
|
||||||
|
});
|
||||||
|
|
||||||
|
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
||||||
|
|
||||||
|
assertEquals(uuid, netByteBuf.readUUID());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void readBytes() throws IOException {
|
||||||
|
byte[] bytes = new byte[128];
|
||||||
|
random.nextBytes(bytes);
|
||||||
|
baos.write(bytes);
|
||||||
|
|
||||||
|
byte[] actualBytes = new byte[128];
|
||||||
|
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
||||||
|
|
||||||
|
assertEquals(bytes.length, netByteBuf.readableBytes());
|
||||||
|
|
||||||
|
netByteBuf.readBytes(actualBytes);
|
||||||
|
|
||||||
|
assertArrayEquals(bytes, actualBytes);
|
||||||
|
assertEquals(0, netByteBuf.readableBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void read_offset() throws IOException {
|
||||||
|
byte[] bytes = new byte[128];
|
||||||
|
random.nextBytes(bytes);
|
||||||
|
baos.write(bytes);
|
||||||
|
|
||||||
|
byte[] buff = new byte[128];
|
||||||
|
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
||||||
|
netByteBuf.readBytes(buff, 3, 11);
|
||||||
|
|
||||||
|
byte[] expectedBytes = new byte[11];
|
||||||
|
System.arraycopy(bytes, 0, expectedBytes, 0, 11);
|
||||||
|
byte[] actualBytes = new byte[11];
|
||||||
|
System.arraycopy(buff, 3, actualBytes, 0, 11);
|
||||||
|
|
||||||
|
assertArrayEquals(expectedBytes, actualBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private static Stream<Arguments> paramsReadBoolean() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of((byte) 0x00, false),
|
||||||
|
Arguments.of((byte) 0x01, true)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private static Stream<Arguments> paramsReadUnsignedByte() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of((byte) 30, 30),
|
||||||
|
Arguments.of((byte) (0xFF & 130), 130)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private static Stream<Arguments> paramsReadString() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(""),
|
||||||
|
Arguments.of("Latin"),
|
||||||
|
Arguments.of("Кириллица"),
|
||||||
|
Arguments.of("العربية"),
|
||||||
|
Arguments.of("ﬦﬣﬡ"), // Алфавитные формы представления
|
||||||
|
Arguments.of("\uD800\uDD07") // Эгейские цифры, [один]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
246
src/test/java/mc/protocol/io/NetByteBufWriteTest.java
Normal file
246
src/test/java/mc/protocol/io/NetByteBufWriteTest.java
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
package mc.protocol.io;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
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.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 NetByteBufWriteTest {
|
||||||
|
|
||||||
|
private static Random random;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void setUp() {
|
||||||
|
random = new Random(System.currentTimeMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("paramsWriteBoolean")
|
||||||
|
void writeBoolean(boolean sourceValue, byte expectedByte) {
|
||||||
|
ByteBuf byteBuf = Unpooled.buffer();
|
||||||
|
NetByteBuf netByteBuf = new NetByteBuf(byteBuf);
|
||||||
|
|
||||||
|
netByteBuf.writeBoolean(sourceValue);
|
||||||
|
|
||||||
|
assertEquals(expectedByte, byteBuf.array()[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("paramsWriteByte")
|
||||||
|
void writeByte(byte sourceValue, byte expectedByte) {
|
||||||
|
ByteBuf byteBuf = Unpooled.buffer();
|
||||||
|
NetByteBuf netByteBuf = new NetByteBuf(byteBuf);
|
||||||
|
|
||||||
|
netByteBuf.writeByte(sourceValue);
|
||||||
|
|
||||||
|
assertEquals(expectedByte, byteBuf.array()[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("paramsWriteString")
|
||||||
|
void writeString(String string) {
|
||||||
|
ByteBuf byteBuf = Unpooled.buffer();
|
||||||
|
NetByteBuf netByteBuf = new NetByteBuf(byteBuf);
|
||||||
|
|
||||||
|
netByteBuf.writeString(string);
|
||||||
|
|
||||||
|
byte[] actualArray = netByteBuf.copy(0, netByteBuf.readableBytes()).array();
|
||||||
|
int length = actualArray[0]; // допустим, что размер поместился в один байт
|
||||||
|
assertEquals(string.codePoints().count(), length);
|
||||||
|
|
||||||
|
byte[] dataBytes = new byte[actualArray.length - 1];
|
||||||
|
System.arraycopy(actualArray, 1, dataBytes, 0, dataBytes.length);
|
||||||
|
assertEquals(string, new String(dataBytes, StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
//возможно этот тест нужно перенести в NetByteBufReadTest
|
||||||
|
@Test
|
||||||
|
void writeString_overSize() {
|
||||||
|
String overSizeString = RandomStringUtils.randomAscii(Short.MAX_VALUE + Short.MAX_VALUE);
|
||||||
|
|
||||||
|
ByteBuf byteBuf = Unpooled.buffer();
|
||||||
|
NetByteBuf netByteBuf = new NetByteBuf(byteBuf);
|
||||||
|
|
||||||
|
netByteBuf.writeString(overSizeString);
|
||||||
|
|
||||||
|
NetByteBuf netByteBuf2 = new NetByteBuf(byteBuf.copy());
|
||||||
|
String actualString = netByteBuf2.readString();
|
||||||
|
|
||||||
|
String expectedString = overSizeString.substring(0, Short.MAX_VALUE);
|
||||||
|
|
||||||
|
assertEquals(expectedString, actualString);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("paramsWriteVarInt")
|
||||||
|
void writeVarInt(int sourceValue, byte[] expectedBytes) {
|
||||||
|
ByteBuf byteBuf = Unpooled.buffer();
|
||||||
|
NetByteBuf netByteBuf = new NetByteBuf(byteBuf);
|
||||||
|
|
||||||
|
netByteBuf.writeVarInt(sourceValue);
|
||||||
|
byte[] actualArray = netByteBuf.copy(0, netByteBuf.readableBytes()).array();
|
||||||
|
|
||||||
|
assertArrayEquals(expectedBytes, actualArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource({ "paramsWriteVarInt", "paramsWriteVarLong" })
|
||||||
|
void writeVarLong(long sourceValue, byte[] expectedBytes) {
|
||||||
|
ByteBuf byteBuf = Unpooled.buffer();
|
||||||
|
NetByteBuf netByteBuf = new NetByteBuf(byteBuf);
|
||||||
|
|
||||||
|
netByteBuf.writeVarLong(sourceValue);
|
||||||
|
byte[] actualArray = netByteBuf.copy(0, netByteBuf.readableBytes()).array();
|
||||||
|
|
||||||
|
assertArrayEquals(expectedBytes, actualArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void writeUUID() {
|
||||||
|
final UUID uuid = UUID.randomUUID();
|
||||||
|
|
||||||
|
ByteBuf byteBuf = Unpooled.buffer();
|
||||||
|
NetByteBuf netByteBuf = new NetByteBuf(byteBuf);
|
||||||
|
|
||||||
|
netByteBuf.writeUUID(uuid);
|
||||||
|
|
||||||
|
final long mostSignificantBits = uuid.getMostSignificantBits();
|
||||||
|
final long leastSignificantBits = uuid.getLeastSignificantBits();
|
||||||
|
|
||||||
|
byte[] actualArray = netByteBuf.copy(0, netByteBuf.readableBytes()).array();
|
||||||
|
|
||||||
|
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) },
|
||||||
|
actualArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void writeBytes() {
|
||||||
|
byte[] bytes = new byte[128];
|
||||||
|
random.nextBytes(bytes);
|
||||||
|
|
||||||
|
ByteBuf byteBuf = Unpooled.buffer();
|
||||||
|
NetByteBuf netByteBuf = new NetByteBuf(byteBuf);
|
||||||
|
|
||||||
|
netByteBuf.writeBytes(bytes);
|
||||||
|
byte[] actualArray = netByteBuf.copy(0, netByteBuf.readableBytes()).array();
|
||||||
|
|
||||||
|
assertArrayEquals(bytes, actualArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void write_offset() {
|
||||||
|
byte[] bytes = new byte[128];
|
||||||
|
random.nextBytes(bytes);
|
||||||
|
|
||||||
|
ByteBuf byteBuf = Unpooled.buffer();
|
||||||
|
NetByteBuf netByteBuf = new NetByteBuf(byteBuf);
|
||||||
|
|
||||||
|
netByteBuf.writeBytes(bytes, 3, 11);
|
||||||
|
|
||||||
|
byte[] actualBytes = new byte[11];
|
||||||
|
System.arraycopy(byteBuf.array(), 0, actualBytes, 0, 11);
|
||||||
|
|
||||||
|
byte[] expectedBytes = new byte[11];
|
||||||
|
System.arraycopy(bytes, 3, expectedBytes, 0, 11);
|
||||||
|
|
||||||
|
assertArrayEquals(expectedBytes, actualBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private static Stream<Arguments> paramsWriteBoolean() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(false, (byte) 0x00),
|
||||||
|
Arguments.of(true, (byte) 0x01)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private static Stream<Arguments> paramsWriteString() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(""),
|
||||||
|
Arguments.of("Latin"),
|
||||||
|
Arguments.of("Кириллица"),
|
||||||
|
Arguments.of("العربية"),
|
||||||
|
Arguments.of("ﬦﬣﬡ"), // Алфавитные формы представления
|
||||||
|
Arguments.of("\uD800\uDD07") // Эгейские цифры, [один]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
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 })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
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