0

Handshake + ServerInfo packets

This commit is contained in:
2019-08-28 04:26:14 +03:00
parent cbaf6a980a
commit 54de0503c7
13 changed files with 475 additions and 0 deletions

View File

@@ -23,6 +23,9 @@ dependencies {
/* LOGGER */
compile (group: 'org.slf4j', name: 'slf4j-api', version: slf4j_version)
/* COMPONENTS */
compile (group: 'com.google.guava', name: 'guava', version: '28.0-jre')
/* LOMBOK */
annotationProcessor (group: 'org.projectlombok', name: 'lombok', version: lombok_version)
compileOnly (group: 'org.projectlombok', name: 'lombok', version: lombok_version)

View File

@@ -0,0 +1,8 @@
package mc.protocol;
public class DecoderException extends RuntimeException {
public DecoderException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,106 @@
package mc.protocol;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
public abstract class NetInputStream extends InputStream {
public boolean readBoolean() {
return readByte() >= 0x01;
}
@Override
public int read() throws IOException {
return readByte();
}
// Unsigned Byte [1]
public int readUnsignedShort() {
return readShort() & 0xFFFF;
}
// Int [4]
// Long [8]
// Float [4]
// Double [8]
public String readString() {
return readString(Short.MAX_VALUE);
}
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];
readBytes(bytes);
return new String(bytes, StandardCharsets.UTF_8);
}
// Chat
// Identifier
public int readVarInt() {
int numRead = 0;
int result = 0;
byte read;
do {
if ((numRead + 1) > 5) {
//FIXME выводить в лог предупреждение
break; // VarInt is too big
}
read = readByte();
int value = (read & 0b01111111);
result |= (value << (7 * numRead));
numRead++;
} while ((read & 0b10000000) != 0);
return result;
}
// VarLong
// Entity Metadata
// Slot
// NBT Tag
// Position [8]
// Angle [1]
// UUID [16]
public int readBytes(byte[] buffer) {
return readBytes(buffer, 0, buffer.length);
}
@Override
public int read(byte[] buffer) throws IOException {
return readBytes(buffer);
}
@Override
public int read(byte[] buffer, int offset, int length) throws IOException {
return readBytes(buffer, offset, length);
}
public abstract byte readByte();
public abstract int readBytes(byte[] buffer, int offset, int lengtn);
public abstract int readShort();
}

View File

@@ -0,0 +1,89 @@
package mc.protocol;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
public abstract class NetOutputStream extends OutputStream {
public void writeBoolean(boolean value) {
writeByte(value ? 0x01 : 0x00);
}
@Override
public void write(int b) throws IOException {
writeByte(b);
}
// Unsigned Byte [1]
// Unsigned Short [2]
// Int [4]
// Long [8]
// Float [4]
// Double [8]
public void writeString(String string) {
byte[] buf;
if (string.length() > Short.MAX_VALUE) {
//FIXME нужно выдавать предупреждение в лог
buf = string.substring(0, Short.MAX_VALUE).getBytes(StandardCharsets.UTF_8);
writeVarInt(Short.MAX_VALUE);
} else {
buf = string.getBytes(StandardCharsets.UTF_8);
writeVarInt(string.length());
}
writeBytes(buf);
}
// Chat
// Identifier
public void writeVarInt(int value) {
while ((value & -128) != 0) {
writeByte(value & 127 | 128);
value >>>= 7;
}
writeByte(value);
}
// VarLong
// Entity Metadata
// Slot
// NBT Tag
// Position [8]
// Angle [1]
// UUID [16]
public void writeBytes(byte[] buffer) {
writeBytes(buffer, 0, buffer.length);
}
@Override
public void write(byte[] buffer) throws IOException {
writeBytes(buffer);
}
@Override
public void write(byte[] buffer, int offset, int length) throws IOException {
writeBytes(buffer, offset, length);
}
public abstract void writeByte(int value);
public abstract void writeBytes(byte[] buffer, int offset, int lengtn);
public abstract void writeShort(int value);
}

View File

@@ -0,0 +1,8 @@
package mc.protocol;
public interface Packet {
void readSelf(NetInputStream netInputStream);
void writeSelf(NetOutputStream netOutputStream);
}

View File

@@ -0,0 +1,6 @@
package mc.protocol;
public enum PacketDirection {
SERVER_BOUND, CLIENT_BOUND
}

View File

@@ -0,0 +1,67 @@
package mc.protocol;
import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import mc.protocol.handshake.client.HandshakePacket;
import mc.protocol.status.client.StatusServerRequest;
import mc.protocol.status.server.StatusServerResponse;
@RequiredArgsConstructor
public enum State {
HANDSHAKING(-1){{
setServerBoundPackets(ImmutableBiMap.of(
0x00, HandshakePacket.class
));
}},
PLAY(0),
STATUS(1){{
setServerBoundPackets(ImmutableBiMap.of(
0x00, StatusServerRequest.class
));
setClientBoundPackets(ImmutableBiMap.of(
0x00, StatusServerResponse.class
));
}},
LOGIN(2);
public static State getById(int id) {
for (State state : State.values()) {
if (state.id == id) {
return state;
}
}
return null;
}
@Getter
private final int id;
@Setter(value = AccessLevel.PROTECTED)
private BiMap<Integer, Class<? extends Packet>> clientBoundPackets;
@Setter(value = AccessLevel.PROTECTED)
private BiMap<Integer, Class<? extends Packet>> serverBoundPackets;
public Class<? extends Packet> getPacketById(PacketDirection direction, int id) {
if (direction == PacketDirection.CLIENT_BOUND) {
return clientBoundPackets == null ? null : clientBoundPackets.get(id);
} else {
return serverBoundPackets == null ? null : serverBoundPackets.get(id);
}
}
public Integer getIdByPacket(PacketDirection direction, Class<? extends Packet> clazz) {
if (direction == PacketDirection.CLIENT_BOUND) {
return clientBoundPackets == null ? null : clientBoundPackets.inverse().get(clazz);
} else {
return serverBoundPackets == null ? null : serverBoundPackets.inverse().get(clazz);
}
}
}

View File

@@ -0,0 +1,34 @@
package mc.protocol.coder;
import mc.protocol.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();
}
}

View File

@@ -0,0 +1,45 @@
package mc.protocol.coder;
/*
Packet format:
| FIELD | TYPE | NOTES |
+------------+--------+-----------------------------------+
| SIZE | VarInt | = sizeOf(id) + sizeOf(byte_array) |
| PACKET ID | VarInt | |
| BYTE ARRAY | bytes | |
https://wiki.vg/index.php?title=Protocol&oldid=7368#Without_compression
*/
import lombok.RequiredArgsConstructor;
import mc.protocol.NetInputStream;
import mc.protocol.Packet;
import mc.protocol.PacketDirection;
import mc.protocol.State;
import java.util.Objects;
@RequiredArgsConstructor
public class ProtocolDecoder {
private final PacketDirection direction;
public Packet decode(State state, NetInputStream netInputStream) {
//TODO необходим механизм пропуска необработанных байтов
int sizePacket = netInputStream.readVarInt();
int packetId = netInputStream.readVarInt();
Class<? extends Packet> packetClass = state.getPacketById(direction, packetId);
Objects.requireNonNull(packetClass);
try {
Packet packet = packetClass.newInstance();
packet.readSelf(netInputStream);
return packet;
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace(); //FIXME нужно писать в лог
return null;
}
}
}

View File

@@ -0,0 +1,37 @@
package mc.protocol.coder;
import lombok.RequiredArgsConstructor;
import mc.protocol.*;
import java.util.Objects;
/*
Packet format:
| FIELD | TYPE | NOTES |
+------------+--------+-----------------------------------+
| SIZE | VarInt | = sizeOf(id) + sizeOf(byte_array) |
| PACKET ID | VarInt | |
| BYTE ARRAY | bytes | |
https://wiki.vg/index.php?title=Protocol&oldid=7368#Without_compression
*/
@RequiredArgsConstructor
public class ProtocolEncoder {
private final PacketDirection direction;
public void encode(State state, Packet packet, NetOutputStream netOutputStream) {
Integer packetId = state.getIdByPacket(direction, packet.getClass());
Objects.requireNonNull(packetId);
ByteArrayNetOutputStream banos = new ByteArrayNetOutputStream();
banos.writeVarInt(packetId);
packet.writeSelf(banos);
netOutputStream.writeVarInt(banos.size());
netOutputStream.writeBytes(banos.toByteArray());
}
}

View File

@@ -0,0 +1,32 @@
package mc.protocol.handshake.client;
import lombok.Data;
import mc.protocol.NetInputStream;
import mc.protocol.NetOutputStream;
import mc.protocol.Packet;
import mc.protocol.State;
@Data
public class HandshakePacket implements Packet {
private int protocolVersion;
private String ip;
private int port;
private State nextState;
@Override
public void readSelf(NetInputStream netInputStream) {
protocolVersion = netInputStream.readVarInt();
ip = netInputStream.readString(255);
port = netInputStream.readUnsignedShort();
nextState = State.getById(netInputStream.readVarInt());
}
@Override
public void writeSelf(NetOutputStream netOutputStream) {
netOutputStream.writeVarInt(protocolVersion);
netOutputStream.writeString(ip);
netOutputStream.writeShort(port);
netOutputStream.writeVarInt(nextState.getId());
}
}

View File

@@ -0,0 +1,18 @@
package mc.protocol.status.client;
import mc.protocol.NetInputStream;
import mc.protocol.NetOutputStream;
import mc.protocol.Packet;
public class StatusServerRequest implements Packet {
@Override
public void readSelf(NetInputStream netInputStream) {
// empty
}
@Override
public void writeSelf(NetOutputStream netOutputStream) {
// empty
}
}

View File

@@ -0,0 +1,22 @@
package mc.protocol.status.server;
import lombok.Data;
import mc.protocol.NetInputStream;
import mc.protocol.NetOutputStream;
import mc.protocol.Packet;
@Data
public class StatusServerResponse implements Packet {
private String info;
@Override
public void readSelf(NetInputStream netInputStream) {
info = netInputStream.readString();
}
@Override
public void writeSelf(NetOutputStream netOutputStream) {
netOutputStream.writeString(info);
}
}