org.dragonet.net.DragonetSession.java Source code

Java tutorial

Introduction

Here is the source code for org.dragonet.net.DragonetSession.java

Source

/*
 * GNU LESSER GENERAL PUBLIC LICENSE
 *                       Version 3, 29 June 2007
 *
 * Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
 * Everyone is permitted to copy and distribute verbatim copies
 * of this license document, but changing it is not allowed.
 *
 * You can view LICENCE file for details. 
 *
 * @author The Dragonet Team
 */
package org.dragonet.net;

import com.flowpowered.networking.Message;
import com.flowpowered.networking.exception.ChannelClosedException;
import com.flowpowered.networking.protocol.AbstractProtocol;
import io.netty.channel.ChannelFuture;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.crypto.SecretKey;
import lombok.Getter;
import lombok.Setter;
import net.glowstone.EventFactory;
import net.glowstone.GlowServer;
import net.glowstone.entity.GlowPlayer;
import net.glowstone.entity.meta.profile.PlayerProfile;
import net.glowstone.io.PlayerDataService;
import net.glowstone.net.GlowSession;
import net.glowstone.net.message.KickMessage;
import net.glowstone.net.message.play.game.UserListItemMessage;
import net.glowstone.net.pipeline.CodecsHandler;
import net.glowstone.net.protocol.GlowProtocol;
import net.glowstone.net.protocol.ProtocolType;
import org.apache.commons.lang.ArrayUtils;
import org.bukkit.GameMode;
import org.bukkit.event.player.PlayerKickEvent;
import org.bukkit.event.player.PlayerLoginEvent;
import org.dragonet.DragonetServer;
import org.dragonet.entity.DragonetPlayer;
import org.dragonet.net.packet.EncapsulatedPacket;
import org.dragonet.net.packet.RaknetDataPacket;
import org.dragonet.net.packet.minecraft.ClientConnectPacket;
import org.dragonet.net.packet.minecraft.LoginPacket;
import org.dragonet.net.packet.minecraft.LoginStatusPacket;
import org.dragonet.net.packet.minecraft.PEPacket;
import org.dragonet.net.packet.minecraft.PEPacketIDs;
import org.dragonet.net.packet.minecraft.PingPongPacket;
import org.dragonet.net.packet.minecraft.ServerHandshakePacket;
import org.dragonet.net.packet.minecraft.StartGamePacket;
import org.dragonet.net.translator.BaseTranslator;
import org.dragonet.net.translator.TranslatorProvider;
import org.dragonet.utilities.MD5Encrypt;
import org.dragonet.utilities.io.PEBinaryReader;
import org.dragonet.utilities.io.PEBinaryWriter;

public class DragonetSession extends GlowSession {

    private final static Pattern patternUsername = Pattern.compile("^[a-zA-Z0-9_]{3,16}$");

    private @Getter DragonetServer dServer;

    private SocketAddress remoteAddress;
    private String remoteIP;
    private int remotePort;
    private InetSocketAddress remoteInetSocketAddress;

    private int loginStage;

    private @Getter long clientID;
    private @Getter short clientMTU;

    private @Getter long clientSessionID;

    private @Getter int sequenceNum; //Server->Client
    private @Getter int lastSequenceNum; //Server<-Client

    private @Getter @Setter int messageIndex; //Server->Client
    private @Getter @Setter int splitID;

    private @Getter String username;

    private RaknetDataPacket queue;

    private ArrayList<Integer> queueACK = new ArrayList<>();
    private ArrayList<Integer> queueNACK = new ArrayList<>();
    private HashMap<Integer, RaknetDataPacket> cachedOutgoingPacket = new HashMap<>();

    private @Getter BaseTranslator translator;

    private @Getter ClientChunkManager chunkManager;

    public DragonetSession(DragonetServer dServer, SocketAddress remoteAddress, long clientID, short clientMTU) {
        super(dServer.getServer());
        this.dServer = dServer;
        this.clientID = clientID;
        this.clientMTU = clientMTU;
        this.remoteAddress = remoteAddress;
        this.remoteIP = this.remoteAddress.toString().substring(1, this.remoteAddress.toString().indexOf(":"));
        this.remotePort = Integer
                .parseInt(this.remoteAddress.toString().substring(this.remoteAddress.toString().indexOf(":") + 1));
        this.remoteInetSocketAddress = new InetSocketAddress(this.remoteIP, this.remotePort);
        this.queue = new RaknetDataPacket(this.sequenceNum);
        this.chunkManager = new ClientChunkManager(this);
        this.loginStage = 0;
    }

    /**
     * Trigger a tick update for the session
     */
    public void onTick() {
        sendAllACK();
        sendAllNACK();
        if (this.queue.getEncapsulatedPackets().size() > 0) {
            this.fireQueue();
        }
        this.chunkManager.sendChunks();
    }

    private synchronized void sendAllACK() {
        if (this.queueACK.isEmpty()) {
            return;
        }
        int[] ackSeqs = ArrayUtils.toPrimitive(this.queueACK.toArray(new Integer[0]));
        Arrays.sort(ackSeqs);
        this.queueACK.clear();
        ByteArrayOutputStream allRecBos = new ByteArrayOutputStream();
        PEBinaryWriter allRecWriter = new PEBinaryWriter(allRecBos);
        try {
            int count = ackSeqs.length;
            int records = 0;
            if (count > 0) {
                int pointer = 1;
                int start = ackSeqs[0];
                int last = ackSeqs[0];
                ByteArrayOutputStream recBos = new ByteArrayOutputStream();
                PEBinaryWriter recWriter;
                while (pointer < count) {
                    int current = ackSeqs[pointer++];
                    int diff = current - last;
                    if (diff == 1) {
                        last = current;
                    } else if (diff > 1) { //Forget about duplicated packets (bad queues?)
                        recBos.reset();
                        recWriter = new PEBinaryWriter(recBos);
                        if (start == last) {
                            recWriter.writeByte((byte) 0x01);
                            recWriter.writeTriad(start);
                            start = last = current;
                        } else {
                            recWriter.writeByte((byte) 0x00);
                            recWriter.writeTriad(start);
                            recWriter.writeTriad(last);
                            start = last = current;
                        }
                        records++;
                    }
                }
                if (start == last) {
                    allRecWriter.writeByte((byte) 0x01);
                    allRecWriter.writeTriad(start);
                } else {
                    allRecWriter.writeByte((byte) 0x00);
                    allRecWriter.writeTriad(start);
                    allRecWriter.writeTriad(last);
                }
                records++;
            }
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            PEBinaryWriter writer = new PEBinaryWriter(bos);
            writer.writeByte((byte) 0xC0);
            writer.writeShort((short) (records & 0xFFFF));
            writer.write(allRecBos.toByteArray());
            this.dServer.getNetworkHandler().send(bos.toByteArray(), this.remoteAddress);
        } catch (IOException e) {
        }
    }

    private synchronized void sendAllNACK() {
        if (this.queueNACK.isEmpty()) {
            return;
        }
        int[] ackSeqs = ArrayUtils.toPrimitive(this.queueNACK.toArray(new Integer[0]));
        Arrays.sort(ackSeqs);
        this.queueNACK.clear();
        ByteArrayOutputStream allRecBos = new ByteArrayOutputStream();
        PEBinaryWriter allRecWriter = new PEBinaryWriter(allRecBos);
        try {
            int count = ackSeqs.length;
            int records = 0;
            if (count > 0) {
                int pointer = 1;
                int start = ackSeqs[0];
                int last = ackSeqs[0];
                ByteArrayOutputStream recBos = new ByteArrayOutputStream();
                PEBinaryWriter recWriter;
                while (pointer < count) {
                    int current = ackSeqs[pointer++];
                    int diff = current - last;
                    if (diff == 1) {
                        last = current;
                    } else if (diff > 1) { //Forget about duplicated packets (bad queues?)
                        recBos.reset();
                        recWriter = new PEBinaryWriter(recBos);
                        if (start == last) {
                            recWriter.writeByte((byte) 0x01);
                            recWriter.writeTriad(start);
                            start = last = current;
                        } else {
                            recWriter.writeByte((byte) 0x00);
                            recWriter.writeTriad(start);
                            recWriter.writeTriad(last);
                            start = last = current;
                        }
                        records++;
                    }
                }
                if (start == last) {
                    allRecWriter.writeByte((byte) 0x01);
                    allRecWriter.writeTriad(start);
                } else {
                    allRecWriter.writeByte((byte) 0x00);
                    allRecWriter.writeTriad(start);
                    allRecWriter.writeTriad(last);
                }
                records++;
            }
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            PEBinaryWriter writer = new PEBinaryWriter(bos);
            writer.writeByte((byte) 0xA0);
            writer.writeShort((short) (records & 0xFFFF));
            writer.write(allRecBos.toByteArray());
            this.dServer.getNetworkHandler().send(bos.toByteArray(), this.remoteAddress);
        } catch (IOException e) {
        }
    }

    /**
     * Process a ACK packet
     *
     * @param buffer The ACK packet binary array
     */
    public void processACKPacket(byte[] buffer) {
        try {
            PEBinaryReader reader = new PEBinaryReader(new ByteArrayInputStream(buffer));
            int count = reader.readShort();
            List<Integer> packets = new ArrayList<>();
            for (int i = 0; i < count && reader.available() > 0; ++i) {
                byte[] tmp = new byte[6];
                if (reader.readByte() == (byte) 0x00) {
                    int start = reader.readTriad();
                    int end = reader.readTriad();
                    if ((end - start) > 4096) {
                        end = start + 4096;
                    }
                    for (int c = start; c <= end; ++c) {
                        packets.add(c);
                    }
                } else {
                    packets.add(reader.readTriad());
                }
            }
            int[] seqNums = ArrayUtils.toPrimitive(packets.toArray(new Integer[0]));
            for (int seq : seqNums) {
                if (this.cachedOutgoingPacket.containsKey(seq)) {
                    this.cachedOutgoingPacket.remove(seq);
                }
            }
        } catch (IOException e) {
        }
    }

    /**
     * Process a NACK packet
     *
     * @param buffer The NACK packet binary array
     */
    public void processNACKPacket(byte[] buffer) {
        try {
            PEBinaryReader reader = new PEBinaryReader(new ByteArrayInputStream(buffer));
            int count = reader.readShort();
            List<Integer> packets = new ArrayList<>();
            for (int i = 0; i < count && reader.available() > 0; ++i) {
                byte[] tmp = new byte[6];
                if (reader.readByte() == (byte) 0x00) {
                    int start = reader.readTriad();
                    int end = reader.readTriad();
                    if ((end - start) > 4096) {
                        end = start + 4096;
                    }
                    for (int c = start; c <= end; ++c) {
                        packets.add(c);
                    }
                } else {
                    packets.add(reader.readTriad());
                }
            }
            int[] seqNums = ArrayUtils.toPrimitive(packets.toArray(new Integer[0]));
            for (int seq : seqNums) {
                if (this.cachedOutgoingPacket.containsKey(seq)) {
                    this.dServer.networkHandler.getUdp().send(this.cachedOutgoingPacket.get(seq).getData(),
                            this.remoteAddress);
                }
            }
        } catch (IOException e) {
        }
    }

    /**
     * Send a packet to the client with a reliability defined
     *
     * @param packet Packet to send
     * @param reliability Packet reliability
     */
    public void send(PEPacket packet, int reliability) {
        if (!(packet instanceof PEPacket))
            return;
        packet.encode();
        if (this.queue.getLength() > this.clientMTU) {
            this.fireQueue();
        }
        EncapsulatedPacket[] encapsulatedPacket = EncapsulatedPacket.fromPEPacket(this, packet, reliability);
        for (EncapsulatedPacket ePacket : encapsulatedPacket) {
            ePacket.encode();
            /*
            if (this.queue.getLength() + ePacket.getData().length > this.clientMTU - 24) {
            this.fireQueue();
            }
            */
            this.fireQueue();
            this.queue.getEncapsulatedPackets().add(ePacket);
        }
    }

    /***
     * Send a packet to the client with default packet reliability 2
     * @param packet Packet to send
     */
    public void send(PEPacket packet) {
        this.send(packet, 2);
    }

    private synchronized void fireQueue() {
        this.cachedOutgoingPacket.put(this.queue.getSequenceNumber(), this.queue);
        this.queue.encode();
        this.dServer.getNetworkHandler().getUdp().send(this.queue.getData(), this.remoteAddress);
        this.queue = new RaknetDataPacket(this.sequenceNum++);
    }

    /**
     * Send a message to the client
     *
     * @param message Message to send
     */
    @Override
    public void send(Message message) throws ChannelClosedException {
        PEPacket[] packets = this.translator.translateToPE(message);
        if (packets == null) {
            return;
        }
        for (PEPacket packet : packets) {
            if (packet == null) {
                continue;
            }
            this.send(packet);
        }
    }

    /**
     * Send multiple messages
     *
     * @param messages Messages to send
     */
    @Override
    public void sendAll(Message... messages) throws ChannelClosedException {
        for (Message message : messages) {
            this.send(message);
        }
    }

    /**
     * Send a message to the client
     *
     * @param message Message to send
     * @return Returns nothing, just for implementing the interface
     */
    @Override
    public ChannelFuture sendWithFuture(Message message) {
        this.send(message);
        return null;
    }

    public void processDataPacket(RaknetDataPacket dataPacket) {
        if (dataPacket.getSequenceNumber() - this.lastSequenceNum > 1) {
            for (int i = this.lastSequenceNum + 1; i < dataPacket.getSequenceNumber(); i++) {
                this.queueNACK.add(i);
            }
        } else {
            this.lastSequenceNum = dataPacket.getSequenceNumber();
        }
        this.queueACK.add(dataPacket.getSequenceNumber());
        if (dataPacket.getEncapsulatedPackets().isEmpty()) {
            return;
        }
        for (EncapsulatedPacket epacket : dataPacket.getEncapsulatedPackets()) {
            PEPacket packet = PEPacket.fromBinary(epacket.buffer);
            if (packet == null) {
                continue;
            }
            switch (packet.pid()) {
            case PEPacketIDs.PING:
                PingPongPacket pkPong = new PingPongPacket();
                pkPong.pingID = ((PingPongPacket) packet).pingID;
                this.send(pkPong, 0);
                break;
            case PEPacketIDs.CLIENT_CONNECT:
                if (this.loginStage != 0)
                    break;
                this.clientSessionID = ((ClientConnectPacket) packet).sessionID;
                ServerHandshakePacket pkServerHandshake = new ServerHandshakePacket();
                pkServerHandshake.port = (short) (this.remotePort & 0xFFFF);
                pkServerHandshake.session = this.clientSessionID;
                pkServerHandshake.session2 = 0x04440BA9L;
                this.loginStage = 1;
                this.send(pkServerHandshake);
                break;
            case PEPacketIDs.CLIENT_HANDSHAKE:
                if (this.loginStage != 1)
                    break;
                this.loginStage = 2;
                break;
            case PEPacketIDs.LOGIN_PACKET:
                if (this.loginStage != 2)
                    break;
                LoginPacket packetLogin = (LoginPacket) packet;
                this.username = packetLogin.username;

                this.translator = TranslatorProvider.getByPEProtocolID(this, packetLogin.protocol1);
                if (!(this.translator instanceof BaseTranslator)) {
                    LoginStatusPacket pkLoginStatus = new LoginStatusPacket();
                    pkLoginStatus.status = 2;
                    this.send(pkLoginStatus);
                    this.disconnect("Unsupported game version! ");
                    break;
                }

                LoginStatusPacket pkLoginStatus = new LoginStatusPacket();
                pkLoginStatus.status = 0;
                this.send(pkLoginStatus);

                this.getLogger().info("Sent LoginStatusPacket! ");

                Matcher matcher = patternUsername.matcher(this.username);
                if (!matcher.matches()) {
                    this.disconnect("Bad username! ");
                    break;
                }

                this.loginStage = 3;
                this.setPlayer(new PlayerProfile(this.username,
                        UUID.nameUUIDFromBytes(MD5Encrypt.encryptString(this.username))));
                break;
            default:
                if (this.loginStage != 3)
                    break;
                if (!(this.translator instanceof BaseTranslator))
                    break;
                this.dServer.getThreadPool().submit(new ProcessPEPacketTask(this, packet));
                break;
            }
        }
    }

    @Override
    public void disconnect(String reason) {
        super.disconnect(reason);
        this.dServer.getNetworkHandler().removeSession(this);
    }

    @Override
    public boolean isActive() {
        return true;
    }

    /**
     * Sets the player associated with this session.
     *
     * @param profile The player's profile with name and UUID information.
     * @throws IllegalStateException if there is already a player associated
     * with this session.
     */
    @Override
    public void setPlayer(PlayerProfile profile) {
        if (this.getPlayer() != null) {
            throw new IllegalStateException("Cannot set player twice");
        }

        // isActive check here in case player disconnected during authentication
        if (!isActive()) {
            // no need to call onDisconnect() since it only does anything if there's a player set
            return;
        }

        // initialize the player
        PlayerDataService.PlayerReader reader = this.getServer().getPlayerDataService()
                .beginReadingData(profile.getUniqueId());
        //this.player = new GlowPlayer(this, profile, reader);
        this.player = new DragonetPlayer(this, profile, reader);

        // isActive check here in case player disconnected after authentication,
        // but before the GlowPlayer initialization was completed
        if (!isActive()) {
            onDisconnect();
            return;
        }

        // login event
        PlayerLoginEvent event = EventFactory.onPlayerLogin(player, this.getHostname());
        if (event.getResult() != PlayerLoginEvent.Result.ALLOWED) {
            disconnect(event.getKickMessage(), true);
            return;
        }

        // Kick other players with the same UUID
        for (GlowPlayer other : getServer().getOnlinePlayers()) {
            if (other != player && other.getUniqueId().equals(player.getUniqueId())) {
                other.getSession().disconnect("You logged in from another location.", true);
                break;
            }
        }

        player.getWorld().getRawPlayers().add(player);

        GlowServer.logger
                .info(player.getName() + " [" + this.getAddress() + "] connected, UUID: " + player.getUniqueId());

        //Send the StartGamePacket
        StartGamePacket pkStartGame = new StartGamePacket();
        pkStartGame.seed = 0;
        pkStartGame.generator = 1;
        if (this.player.getGameMode().equals(GameMode.SURVIVAL)) {
            pkStartGame.gamemode = 0;
        } else if (this.player.getGameMode().equals(GameMode.CREATIVE)) {
            pkStartGame.gamemode = 1;
        } else {
            pkStartGame.gamemode = 0;
        }
        pkStartGame.eid = this.player.getEntityId();
        pkStartGame.spawnX = this.player.getWorld().getSpawnLocation().getBlockX();
        pkStartGame.spawnY = this.player.getWorld().getSpawnLocation().getBlockY();
        pkStartGame.spawnZ = this.player.getWorld().getSpawnLocation().getBlockZ();
        pkStartGame.x = (float) this.player.getLocation().getX();
        pkStartGame.y = (float) this.player.getLocation().getY();
        pkStartGame.z = (float) this.player.getLocation().getZ();
        this.send(pkStartGame);

        // message and user list
        String message = EventFactory.onPlayerJoin(player).getJoinMessage();
        if (message != null && !message.isEmpty()) {
            this.getServer().broadcastMessage(message);
        }

        // todo: display names are included in the outgoing messages here, but
        // don't show up on the client. A workaround or proper fix is needed.
        Message addMessage = new UserListItemMessage(UserListItemMessage.Action.ADD_PLAYER,
                player.getUserListEntry());
        List<UserListItemMessage.Entry> entries = new ArrayList<>();
        for (GlowPlayer other : this.getServer().getOnlinePlayers()) {
            if (other != player && other.canSee(player)) {
                other.getSession().send(addMessage);
            }
            if (player.canSee(other)) {
                entries.add(other.getUserListEntry());
            }
        }
        send(new UserListItemMessage(UserListItemMessage.Action.ADD_PLAYER, entries));
    }

    /**
     * Disconnects the session with the specified reason. This causes a
     * KickMessage to be sent. When it has been delivered, the channel
     * is closed.
     * @param reason The reason for disconnection.
     * @param overrideKick Whether to skip the kick event.
     */
    @Override
    public void disconnect(String reason, boolean overrideKick) {
        if (player != null && !overrideKick) {
            PlayerKickEvent event = EventFactory.onPlayerKick(player, reason);
            if (event.isCancelled()) {
                return;
            }

            reason = event.getReason();

            if (event.getLeaveMessage() != null) {
                this.getServer().broadcastMessage(event.getLeaveMessage());
            }
        }

        // log that the player was kicked
        if (player != null) {
            GlowServer.logger.info(player.getName() + " kicked: " + reason);
        } else {
            GlowServer.logger.info("[" + this.remoteIP + ":" + this.remotePort + "] kicked: " + reason);
        }

        this.send(new KickMessage(reason));
    }

    @Override
    public InetSocketAddress getAddress() {
        return this.remoteInetSocketAddress;
    }

    @Override
    public String getHostname() {
        return this.remoteIP;
    }

    @Override
    public void enableCompression(int threshold) {
    }

    @Override
    public void enableEncryption(SecretKey sharedSecret) {
    }

    @Override
    public void setProtocol(ProtocolType protocol) {
        //GlowProtocol proto = protocol.getProtocol();
        //super.setProtocol(proto);
    }
}