org.dragonet.net.inf.mcpe.PENetworkClient.java Source code

Java tutorial

Introduction

Here is the source code for org.dragonet.net.inf.mcpe.PENetworkClient.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.inf.mcpe;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang.ArrayUtils;
import org.dragonet.net.DragonetSession;
import org.dragonet.net.packet.EncapsulatedPacket;
import org.dragonet.net.packet.Protocol;
import org.dragonet.net.packet.RaknetDataPacket;
import org.dragonet.net.packet.minecraft.BatchPacket;
import org.dragonet.net.packet.minecraft.ClientConnectPacket;
import org.dragonet.net.packet.minecraft.DisconnectPacket;
import org.dragonet.net.packet.minecraft.FullChunkPacket;
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.translator.BaseTranslator;
import org.dragonet.net.translator.TranslatorProvider;
import org.dragonet.utilities.io.PEBinaryReader;
import org.dragonet.utilities.io.PEBinaryWriter;

public final class PENetworkClient {

    @Getter
    private SocketAddress remoteAddress;

    @Getter
    private String remoteIP;

    @Getter
    private int remotePort;

    @Getter
    private InetSocketAddress remoteInetSocketAddress;

    @Getter
    @Setter
    private int loginStage;

    @Getter
    private long clientID;

    @Getter
    private short clientMTU;

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

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

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

    @Getter
    @Setter
    private int splitID;

    private RaknetDataPacket queue;

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

    //Handle spltted packets. 
    private final ConcurrentHashMap<Integer, ByteArrayOutputStream> splits = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<Integer, Integer> splitCounter = new ConcurrentHashMap<>();

    @Getter
    @Setter
    private int sentAndReceivedChunks = 0;

    private ArrayList<Integer> chunkPacketIDS = new ArrayList<>();

    @Getter
    private ArrayDeque<PEPacket> queueAfterChunkSent = new ArrayDeque<>();

    @Getter
    private final long timeConnected;

    @Getter
    private long lastPacketReceived;

    private final NetworkHandler handler;

    /**
     * The session which this client binds to. 
     */
    @Getter
    private DragonetSession session;

    public PENetworkClient(NetworkHandler handler, SocketAddress remoteAddress, long clientID, short clientMTU) {
        this.handler = handler;
        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.loginStage = 0;
        this.timeConnected = System.currentTimeMillis();
        this.lastPacketReceived = System.currentTimeMillis();
    }

    public void setSession(DragonetSession session) {
        if (this.session != null) {
            throw new IllegalStateException("There is already a session bound to this session! ");
        }
        this.session = session;
    }

    public void onTick() {
        sendAllACK();
        sendAllNACK();
        if (this.queue.getEncapsulatedPackets().size() > 0) {
            this.fireQueue();
        }
        if (System.currentTimeMillis() - this.lastPacketReceived > 15000) {
            this.disconnect("Timeout! ");
        }
    }

    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());
            handler.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());
            handler.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) {
                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);
                }
                if (this.chunkPacketIDS.contains(seq)) {
                    this.sentAndReceivedChunks++;
                    this.chunkPacketIDS.remove(new Integer(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) {
                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)) {
                    handler.getUdp().send(this.cachedOutgoingPacket.get(seq).getData(), this.remoteAddress);
                }
            }
        } catch (IOException e) {
        }
    }

    public void sendPacket(PEPacket packet) {
        sendPacket(packet, 2);
    }

    public void sendPacket(PEPacket packet, int reliability) {
        if (!(packet instanceof PEPacket)) {
            return;
        }
        /*
         if (!(packet instanceof FullChunkPacket) && !(packet instanceof StartGamePacket) && !(packet instanceof SetTimePacket) && !(packet instanceof SetDifficultyPacket)
         && !(packet instanceof LoginStatusPacket) && !(packet instanceof ServerHandshakePacket) && this.sentAndReceivedChunks != -1) {
         this.queueAfterChunkSent.add(packet);
         return;
         }*/
        packet.encode();
        if (packet.getData().length > this.clientMTU + 1 && !(packet instanceof BatchPacket)) {
            //BATCH PACKET
            BatchPacket pk = new BatchPacket();
            pk.packets.add(packet);
            sendPacket(pk, reliability);
            //System.out.println("Using BATCH PACKET for " + packet.getClass().getSimpleName());
            return;
        }
        this.fireQueue();
        //System.out.println(" >>*>> Sending: " + packet.getClass().getSimpleName());
        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.queue.getEncapsulatedPackets().add(ePacket);
            if (this.sentAndReceivedChunks != -1 && (packet instanceof FullChunkPacket)
                    && !this.chunkPacketIDS.contains(this.queue.getSequenceNumber())) {
                this.chunkPacketIDS.add(this.queue.getSequenceNumber());
            }
            this.fireQueue();
        }
    }

    private synchronized void fireQueue() {
        if (this.queue.getEncapsulatedPackets().isEmpty()) {
            return;
        }
        this.cachedOutgoingPacket.put(this.queue.getSequenceNumber(), this.queue);
        this.queue.encode();
        handler.getUdp().send(this.queue.getData(), this.remoteAddress);
        this.queue = new RaknetDataPacket(this.sequenceNum++);
    }

    public void disconnect(String reason) {
        this.sendPacket(new DisconnectPacket(reason));
        handler.remove(this.remoteAddress);
    }

    public void processDataPacket(RaknetDataPacket dataPacket) {
        this.lastPacketReceived = System.currentTimeMillis();
        if (dataPacket.getSequenceNumber() - this.lastSequenceNum > 1) {
            for (int i = this.lastSequenceNum + 1; i < dataPacket.getSequenceNumber(); i++) {
                this.queueNACK.add(i);
            }
        }
        this.lastSequenceNum = dataPacket.getSequenceNumber();
        this.queueACK.add(dataPacket.getSequenceNumber());
        if (dataPacket.getEncapsulatedPackets().isEmpty()) {
            return;
        }
        for (EncapsulatedPacket epacket : dataPacket.getEncapsulatedPackets()) {
            if (epacket.hasSplit) {
                System.out.println("PROCESSING SPLITTED PACKET ID: " + epacket.splitID + ", Index-"
                        + epacket.splitIndex + ", Count-" + epacket.splitCount);
                //Handle split packet
                if (epacket.splitIndex == epacket.splitCount - 1) {
                    if (splits.containsKey((Integer) epacket.splitID)) {
                        try {
                            splits.get((Integer) epacket.splitID).write(epacket.buffer);
                        } catch (IOException ex) {
                            ex.printStackTrace();
                        }
                        byte[] buff = splits.get((Integer) epacket.splitID).toByteArray();
                        splits.remove((Integer) epacket.splitID);
                        splitCounter.remove((Integer) epacket.splitID);
                        processPacketBuffer(buff);
                    }
                } else {
                    try {
                        if (epacket.splitIndex == 0) {
                            ByteArrayOutputStream oup = new ByteArrayOutputStream();
                            oup.write(epacket.buffer);
                            splits.put((Integer) epacket.splitID, oup);
                            splitCounter.put((Integer) epacket.splitID, -1);
                        } else {
                            if (splits.containsKey((Integer) epacket.splitID)
                                    && (splitCounter.get((Integer) epacket.splitID) < epacket.splitIndex)) {
                                splits.get((Integer) epacket.splitID).write(epacket.buffer);
                                splitCounter.put((Integer) epacket.splitID, epacket.splitIndex);
                            }
                        }
                    } catch (IOException ex) {
                    }
                }
                continue;
            }
            processPacketBuffer(epacket.buffer);
        }
    }

    private void processPacketBuffer(byte[] buffer) {
        PEPacket packet = Protocol.decode(buffer);
        if (packet == null) {
            return;
        }
        System.out.println("Received Packet: " + packet.getClass().getSimpleName());
        switch (packet.pid()) {
        case PEPacketIDs.PING:
            PingPongPacket pkPong = new PingPongPacket();
            pkPong.pingID = ((PingPongPacket) packet).pingID;
            this.sendPacket(pkPong, 0);
            break;
        case PEPacketIDs.CLIENT_CONNECT:
            if (this.loginStage != 0) {
                break;
            }
            this.clientID = ((ClientConnectPacket) packet).sessionID;
            ServerHandshakePacket pkServerHandshake = new ServerHandshakePacket();
            pkServerHandshake.addr = remoteInetSocketAddress.getAddress();
            pkServerHandshake.port = (short) 0;
            pkServerHandshake.session = this.clientID;
            pkServerHandshake.session2 = this.clientID + 1000L;
            this.loginStage = 1;
            this.sendPacket(pkServerHandshake);
            break;
        case PEPacketIDs.CLIENT_HANDSHAKE:
            if (this.loginStage != 1) {
                break;
            }
            this.loginStage = 2;
            break;
        }
        if (session == null) {
            disconnect("Network error! ");
            return;
        }
        session.onPacketReceived(packet);
    }
}