package ru.dbotthepony.kstarbound.network import com.google.common.util.concurrent.ThreadFactoryBuilder import io.netty.channel.Channel import io.netty.channel.ChannelHandlerContext import io.netty.channel.ChannelInboundHandlerAdapter import io.netty.channel.ChannelOption import io.netty.channel.nio.NioEventLoopGroup import org.apache.logging.log4j.LogManager import ru.dbotthepony.kstarbound.network.packets.DisconnectPacket import ru.dbotthepony.kstarbound.network.packets.HelloListener import ru.dbotthepony.kstarbound.network.packets.HelloPacket import ru.dbotthepony.kstarbound.player.Avatar import ru.dbotthepony.kstarbound.world.entities.PlayerEntity import java.util.* abstract class Connection(val side: ConnectionSide, val type: ConnectionType, val localUUID: UUID) : ChannelInboundHandlerAdapter(), IConnectionDetails { abstract override fun channelRead(ctx: ChannelHandlerContext, msg: Any) var avatar: Avatar? = null var character: PlayerEntity? = null protected var channel: Channel? = null protected var otherSide: HelloPacket? = null protected var helloListener: HelloListener? = null override val protocolVersion: Int get() = otherSide?.protocolVersion ?: 0 override val engineVersion: String get() = otherSide?.engineVersion ?: "0.0.0" override val username: String get() = otherSide?.username ?: "" override val password: String get() = otherSide?.password ?: "" override val uuid: UUID get() = otherSide?.uuid ?: EMPTY_UUID fun bind(channel: Channel) { check(this.channel == null) { "Already having channel bound" } channel.config().setOption(ChannelOption.TCP_NODELAY, true) this.channel = channel if (side == ConnectionSide.SERVER) helloListener = HelloListener(this, channel) } protected abstract fun onHelloReceived(helloPacket: HelloPacket) protected abstract fun inGame() fun helloReceived(helloPacket: HelloPacket) { LOGGER.info("Handshake received on $side from ${channel?.remoteAddress()}: $helloPacket") otherSide = helloPacket if (side == ConnectionSide.SERVER) { helloListener!!.sendHello(localUUID) } onHelloReceived(helloPacket) initializeHandlers() } fun helloFailed() { channel!!.close() } fun initializeHandlers() { val channel = channel ?: throw IllegalStateException("No network channel is bound") if (type == ConnectionType.NETWORK) { channel.pipeline().addLast(PacketRegistry.NATIVE.Inbound(side)) } else { channel.pipeline().addLast(PacketRegistry.NATIVE.InboundValidator(side)) } channel.pipeline().addLast(this) if (type == ConnectionType.NETWORK) { channel.pipeline().addLast(PacketRegistry.NATIVE.Outbound(side)) channel.pipeline().addFirst(DatagramEncoder) channel.pipeline().addFirst(DatagramDecoder()) } else { channel.pipeline().addLast(PacketRegistry.NATIVE.OutboundValidator(side)) } inGame() } fun send(packet: IPacket) { val channel = channel ?: throw IllegalStateException("No network channel is bound") channel.write(packet) } fun sendAndFlush(packet: IPacket) { val channel = channel ?: throw IllegalStateException("No network channel is bound") channel.write(packet) channel.flush() } fun flush() { val channel = channel ?: throw IllegalStateException("No network channel is bound") channel.flush() } fun disconnect(reason: String) { if (side == ConnectionSide.CLIENT) { channel!!.close() } else { channel!!.write(DisconnectPacket(reason)) channel!!.flush() channel!!.close() } } companion object { private val EMPTY_UUID = UUID(0L, 0L) private val LOGGER = LogManager.getLogger() val NIO_POOL by lazy { NioEventLoopGroup(ThreadFactoryBuilder().setDaemon(true).setNameFormat("Starbound Network IO %d").build()) } } }