diff --git a/mcserver-manager/build.gradle b/mcserver-manager/build.gradle index c896d26..64d17d2 100644 --- a/mcserver-manager/build.gradle +++ b/mcserver-manager/build.gradle @@ -1,16 +1,31 @@ group = 'asys' -version = '0.3-SNAPSHOT' +version = '0.4-SNAPSHOT' apply plugin: 'osgi' +configurations { + included + compile.extendsFrom included +} + jar { + dependsOn configurations.included + manifest { name = 'ASys MC server manager' instruction 'Bundle-Activator', 'asys.mcsmanager.Activator' + instruction 'Import-Package', '!io.netty.*', 'javax.security.cert', 'org.slf4j.helpers', '*' } + + from { configurations.included.collect { it.isDirectory() ? it : zipTree(it).matching{exclude{it.path.contains('META-INF')} } } } +} + +ext { + nettyVersion = '4.1.9.Final' } dependencies { compile project(':core') compile project(':webinterface') + included group: 'io.netty', name: 'netty-codec', version: nettyVersion } diff --git a/mcserver-manager/src/main/java/asys/mcsmanager/Activator.java b/mcserver-manager/src/main/java/asys/mcsmanager/Activator.java index 969dd9b..53adeca 100644 --- a/mcserver-manager/src/main/java/asys/mcsmanager/Activator.java +++ b/mcserver-manager/src/main/java/asys/mcsmanager/Activator.java @@ -4,6 +4,7 @@ */ package asys.mcsmanager; +import asys.mcsmanager.server.Server; import asys.webinterface.api.Webinterface; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; @@ -18,25 +19,32 @@ public class Activator implements BundleActivator, ServiceListener { private ServiceTracker serviceTracker; private MCSM_WebModule module; private Webinterface webinterface; + private Server serverManager; @Override public void start(BundleContext context) throws Exception { module = new MCSM_WebModule(); + logger.debug("Get service: {}", Webinterface.class); + serviceTracker = new ServiceTracker<>(context, Webinterface.class, null); + logger.debug("Register service listener"); context.addServiceListener(this); - logger.debug("Get service: {}", Webinterface.class); - serviceTracker = new ServiceTracker<>(context, Webinterface.class, null); + logger.debug("Start server manager: {}:{}", "127.0.0.1", 8779); + serverManager = new Server(); + serverManager.start("127.0.0.1", 8779); } @Override public void stop(BundleContext context) throws Exception { + serverManager.shutdown(); + if (webinterface != null) { webinterface.removeModule(module.getName()); } - serviceTracker.close(); context.removeServiceListener(this); + serviceTracker.close(); } @Override diff --git a/mcserver-manager/src/main/java/asys/mcsmanager/packets/CS_Handshake.java b/mcserver-manager/src/main/java/asys/mcsmanager/packets/CS_Handshake.java new file mode 100644 index 0000000..247f1ef --- /dev/null +++ b/mcserver-manager/src/main/java/asys/mcsmanager/packets/CS_Handshake.java @@ -0,0 +1,40 @@ +/* + * DmitriyMX + * 2017-04-26 + */ +package asys.mcsmanager.packets; + +import io.netty.buffer.ByteBuf; + +public class CS_Handshake extends Packet { + private String clientId; + private String passcode; + + public CS_Handshake() { + } + + public CS_Handshake(String clientId, String passcode) { + this.clientId = clientId; + this.passcode = passcode; + } + + public String getClientId() { + return clientId; + } + + public String getPasscode() { + return passcode; + } + + @Override + public void readSelfData(ByteBuf buffer) { + this.clientId = this.readString(buffer); + this.passcode = this.readString(buffer); + } + + @Override + public void writeSelfData(ByteBuf buffer) { + this.writeString(buffer, clientId); + this.writeString(buffer, passcode); + } +} diff --git a/mcserver-manager/src/main/java/asys/mcsmanager/packets/CS_Ping.java b/mcserver-manager/src/main/java/asys/mcsmanager/packets/CS_Ping.java new file mode 100644 index 0000000..48d73a6 --- /dev/null +++ b/mcserver-manager/src/main/java/asys/mcsmanager/packets/CS_Ping.java @@ -0,0 +1,48 @@ +/* + * DmitriyMX + * 2017-04-26 + */ +package asys.mcsmanager.packets; + +import io.netty.buffer.ByteBuf; + +public class CS_Ping extends Packet { + private long time; + private int tps; + private int countPlayers; + + public CS_Ping() { + } + + public CS_Ping(long time, int tps, int countPlayers) { + this.time = time; + this.tps = tps; + this.countPlayers = countPlayers; + } + + public long getTime() { + return time; + } + + public int getTps() { + return tps; + } + + public int getCountPlayers() { + return countPlayers; + } + + @Override + public void readSelfData(ByteBuf buffer) { + this.time = buffer.readLong(); + this.tps = buffer.readUnsignedByte(); + this.countPlayers = buffer.readUnsignedShort(); + } + + @Override + public void writeSelfData(ByteBuf buffer) { + buffer.writeLong(time); + buffer.writeByte(tps); + buffer.writeShort(countPlayers); + } +} diff --git a/mcserver-manager/src/main/java/asys/mcsmanager/packets/IPacketHandler.java b/mcserver-manager/src/main/java/asys/mcsmanager/packets/IPacketHandler.java new file mode 100644 index 0000000..8cb88f3 --- /dev/null +++ b/mcserver-manager/src/main/java/asys/mcsmanager/packets/IPacketHandler.java @@ -0,0 +1,11 @@ +/* + * DmitriyMX + * 2017-04-26 + */ +package asys.mcsmanager.packets; + +import io.netty.channel.ChannelHandlerContext; + +public interface IPacketHandler { + void handle(Packet packet, ChannelHandlerContext context); +} diff --git a/mcserver-manager/src/main/java/asys/mcsmanager/packets/Packet.java b/mcserver-manager/src/main/java/asys/mcsmanager/packets/Packet.java new file mode 100644 index 0000000..db9b211 --- /dev/null +++ b/mcserver-manager/src/main/java/asys/mcsmanager/packets/Packet.java @@ -0,0 +1,34 @@ +/* + * DmitriyMX + * 2017-04-26 + */ +package asys.mcsmanager.packets; + +import io.netty.buffer.ByteBuf; + +public abstract class Packet { + public String readString(ByteBuf buffer) { + int length = buffer.readUnsignedShort(); + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + sb.append(buffer.readChar()); + } + + return sb.toString(); + } + + public void writeString(ByteBuf buffer, String string) { + int length = string.length(); + buffer.writeShort(length); + + if (length > 0) { + for (int i = 0; i < length; i++) { + buffer.writeChar(string.charAt(i)); + } + } + } + + public abstract void readSelfData(ByteBuf buffer); + public abstract void writeSelfData(ByteBuf buffer); +} diff --git a/mcserver-manager/src/main/java/asys/mcsmanager/packets/SC_HandshakeResult.java b/mcserver-manager/src/main/java/asys/mcsmanager/packets/SC_HandshakeResult.java new file mode 100644 index 0000000..3c6835f --- /dev/null +++ b/mcserver-manager/src/main/java/asys/mcsmanager/packets/SC_HandshakeResult.java @@ -0,0 +1,40 @@ +/* + * DmitriyMX + * 2017-04-26 + */ +package asys.mcsmanager.packets; + +import io.netty.buffer.ByteBuf; + +public class SC_HandshakeResult extends Packet { + private int errorCode; + private String message; + + public SC_HandshakeResult() { + } + + public SC_HandshakeResult(int errorCode, String message) { + this.errorCode = errorCode; + this.message = message; + } + + public int getErrorCode() { + return errorCode; + } + + public String getMessage() { + return message; + } + + @Override + public void readSelfData(ByteBuf buffer) { + this.errorCode = buffer.readUnsignedByte(); + this.message = this.readString(buffer); + } + + @Override + public void writeSelfData(ByteBuf buffer) { + buffer.writeByte(errorCode); + this.writeString(buffer, message); + } +} diff --git a/mcserver-manager/src/main/java/asys/mcsmanager/packets/codec/PacketDecoder.java b/mcserver-manager/src/main/java/asys/mcsmanager/packets/codec/PacketDecoder.java new file mode 100644 index 0000000..ea926a8 --- /dev/null +++ b/mcserver-manager/src/main/java/asys/mcsmanager/packets/codec/PacketDecoder.java @@ -0,0 +1,30 @@ +/* + * DmitriyMX + * 2017-04-26 + */ +package asys.mcsmanager.packets.codec; + +import asys.mcsmanager.packets.Packet; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ReplayingDecoder; + +import java.util.List; + +import static asys.mcsmanager.packets.codec.Params.KNOWN_HANDLERS; +import static asys.mcsmanager.packets.codec.Params.KNOWN_PACKETS; + +public class PacketDecoder extends ReplayingDecoder { + @Override + protected void decode(ChannelHandlerContext contect, ByteBuf inBuf, List out) throws Exception { + int id = inBuf.readUnsignedByte(); + Class pktClass = contect.channel().attr(KNOWN_PACKETS).get().get(id); + if (pktClass == null) return; + + if (contect.channel().attr(KNOWN_HANDLERS).get().containsKey(pktClass)) { + Packet packet = pktClass.newInstance(); + packet.readSelfData(inBuf); + out.add(packet); + } + } +} diff --git a/mcserver-manager/src/main/java/asys/mcsmanager/packets/codec/PacketEncoder.java b/mcserver-manager/src/main/java/asys/mcsmanager/packets/codec/PacketEncoder.java new file mode 100644 index 0000000..ea4947e --- /dev/null +++ b/mcserver-manager/src/main/java/asys/mcsmanager/packets/codec/PacketEncoder.java @@ -0,0 +1,23 @@ +/* + * DmitriyMX + * 2017-04-26 + */ +package asys.mcsmanager.packets.codec; + +import asys.mcsmanager.packets.Packet; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; + +import static asys.mcsmanager.packets.codec.Params.KNOWN_PACKETS; + +public class PacketEncoder extends MessageToByteEncoder { + @Override + protected void encode(ChannelHandlerContext context, Packet packet, ByteBuf outBuf) throws Exception { + Integer id = context.channel().attr(KNOWN_PACKETS).get().inverse().get(packet.getClass()); + if (id == null) return; //TODO в логгере хорошо бы информировать о дропе исходящего пакета + + outBuf.writeByte(id); + packet.writeSelfData(outBuf); + } +} diff --git a/mcserver-manager/src/main/java/asys/mcsmanager/packets/codec/PacketHandler.java b/mcserver-manager/src/main/java/asys/mcsmanager/packets/codec/PacketHandler.java new file mode 100644 index 0000000..14157c1 --- /dev/null +++ b/mcserver-manager/src/main/java/asys/mcsmanager/packets/codec/PacketHandler.java @@ -0,0 +1,18 @@ +/* + * DmitriyMX + * 2017-04-26 + */ +package asys.mcsmanager.packets.codec; + +import asys.mcsmanager.packets.Packet; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; + +import static asys.mcsmanager.packets.codec.Params.KNOWN_HANDLERS; + +public class PacketHandler extends SimpleChannelInboundHandler { + @Override + protected void channelRead0(ChannelHandlerContext context, Packet packet) throws Exception { + context.channel().attr(KNOWN_HANDLERS).get().get(packet.getClass()).handle(packet, context); + } +} diff --git a/mcserver-manager/src/main/java/asys/mcsmanager/packets/codec/Params.java b/mcserver-manager/src/main/java/asys/mcsmanager/packets/codec/Params.java new file mode 100644 index 0000000..255d55c --- /dev/null +++ b/mcserver-manager/src/main/java/asys/mcsmanager/packets/codec/Params.java @@ -0,0 +1,17 @@ +/* + * DmitriyMX + * 2017-04-26 + */ +package asys.mcsmanager.packets.codec; + +import asys.mcsmanager.packets.Packet; +import asys.mcsmanager.packets.IPacketHandler; +import com.google.common.collect.BiMap; +import io.netty.util.AttributeKey; + +import java.util.Map; + +public final class Params { + public static final AttributeKey>> KNOWN_PACKETS = AttributeKey.newInstance("KNOWN_PACKETS"); + public static final AttributeKey, IPacketHandler>> KNOWN_HANDLERS = AttributeKey.newInstance("KNOWN_HANDLERS"); +} diff --git a/mcserver-manager/src/main/java/asys/mcsmanager/server/HandshakeResult.java b/mcserver-manager/src/main/java/asys/mcsmanager/server/HandshakeResult.java new file mode 100644 index 0000000..86e72c8 --- /dev/null +++ b/mcserver-manager/src/main/java/asys/mcsmanager/server/HandshakeResult.java @@ -0,0 +1,12 @@ +/* + * DmitriyMX + * 2017-04-26 + */ +package asys.mcsmanager.server; + +import asys.mcsmanager.packets.SC_HandshakeResult; + +abstract class HandshakeResult { + static final SC_HandshakeResult OK = new SC_HandshakeResult(0, "OK"); + static final SC_HandshakeResult INVALID_PASSCODE = new SC_HandshakeResult(1, "Invalid passcode"); +} diff --git a/mcserver-manager/src/main/java/asys/mcsmanager/server/Server.java b/mcserver-manager/src/main/java/asys/mcsmanager/server/Server.java new file mode 100644 index 0000000..cdd10b2 --- /dev/null +++ b/mcserver-manager/src/main/java/asys/mcsmanager/server/Server.java @@ -0,0 +1,67 @@ +/* + * DmitriyMX + * 2017-04-26 + */ +package asys.mcsmanager.server; + +import asys.mcsmanager.packets.CS_Ping; +import asys.mcsmanager.packets.Packet; +import asys.mcsmanager.packets.codec.PacketDecoder; +import asys.mcsmanager.packets.codec.PacketEncoder; +import asys.mcsmanager.packets.codec.PacketHandler; +import com.google.common.collect.BiMap; +import com.google.common.collect.ImmutableBiMap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; + +public class Server { + public static final BiMap> knownPackets = ImmutableBiMap.of( + 1, CS_Ping.class + ); + private EventLoopGroup bossGroup, workerGroup; + + public void start(String host, int port) { + bossGroup = new NioEventLoopGroup(1); + workerGroup = new NioEventLoopGroup(); + + ServerBootstrap serverBootstrap = createServerBootstrap(); + try { + serverBootstrap.bind(host, port).sync().channel().closeFuture().sync(); + } catch (InterruptedException e) { + shutdown(); + } + } + + public void shutdown() { + workerGroup.shutdownGracefully(); + bossGroup.shutdownGracefully(); + } + + private ServerBootstrap createServerBootstrap() { + ServerBootstrap bootstrap = new ServerBootstrap(); + + bootstrap.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .childHandler(createChannelInitializer()); + + return bootstrap; + } + + private ChannelInitializer createChannelInitializer() { + return new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel socketChannel) throws Exception { + socketChannel.pipeline().addLast( + new PacketEncoder(), + new PacketDecoder(), + new PacketHandler(), + new ServerPacketHandler() + ); + } + }; + } +} diff --git a/mcserver-manager/src/main/java/asys/mcsmanager/server/ServerPacketHandler.java b/mcserver-manager/src/main/java/asys/mcsmanager/server/ServerPacketHandler.java new file mode 100644 index 0000000..8076e36 --- /dev/null +++ b/mcserver-manager/src/main/java/asys/mcsmanager/server/ServerPacketHandler.java @@ -0,0 +1,46 @@ +/* + * DmitriyMX + * 2017-04-26 + */ +package asys.mcsmanager.server; + +import asys.mcsmanager.packets.CS_Handshake; +import asys.mcsmanager.packets.IPacketHandler; +import asys.mcsmanager.packets.Packet; +import asys.mcsmanager.packets.SC_HandshakeResult; +import asys.mcsmanager.packets.codec.Params; +import com.google.common.collect.BiMap; +import com.google.common.collect.ImmutableBiMap; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; + +class ServerPacketHandler extends ChannelInboundHandlerAdapter implements IPacketHandler { + private static final BiMap> handshakePackets = ImmutableBiMap.of( + 1, CS_Handshake.class, + 2, SC_HandshakeResult.class + ); + private static final String PASSCODE = "testpassphrase"; + + @Override + public void channelActive(ChannelHandlerContext context) throws Exception { + context.channel().attr(Params.KNOWN_PACKETS).set(handshakePackets); + super.channelActive(context); + } + + @Override + public void handle(Packet packet, ChannelHandlerContext context) { + if (packet.getClass() == CS_Handshake.class) handleCSHandshake((CS_Handshake) packet, context); + } + + private void handleCSHandshake(CS_Handshake packet, ChannelHandlerContext context) { + if (!packet.getPasscode().equalsIgnoreCase(PASSCODE)) { + try { + context.channel().writeAndFlush(HandshakeResult.INVALID_PASSCODE).sync().channel().close(); + } catch (InterruptedException ignore) { + // ignore + } + } else { + context.channel().writeAndFlush(HandshakeResult.OK); + } + } +}