    001package net.minecraft.network;
    002
    003import cpw.mods.fml.common.network.FMLNetworkHandler;
    004import cpw.mods.fml.relauncher.Side;
    005import cpw.mods.fml.relauncher.SideOnly;
    006import java.io.BufferedOutputStream;
    007import java.io.DataInputStream;
    008import java.io.DataOutputStream;
    009import java.io.IOException;
    010import java.io.InputStream;
    011import java.net.Socket;
    012import java.net.SocketAddress;
    013import java.net.SocketException;
    014import java.security.PrivateKey;
    015import java.util.ArrayList;
    016import java.util.Collections;
    017import java.util.Iterator;
    018import java.util.List;
    019import java.util.concurrent.atomic.AtomicInteger;
    020import javax.crypto.SecretKey;
    021import net.minecraft.logging.ILogAgent;
    022import net.minecraft.network.packet.NetHandler;
    023import net.minecraft.network.packet.Packet;
    024import net.minecraft.network.packet.Packet252SharedKey;
    025import net.minecraft.util.CryptManager;
    026
    027public class TcpConnection implements INetworkManager
    028{
    029    public static AtomicInteger field_74471_a = new AtomicInteger();
    030    public static AtomicInteger field_74469_b = new AtomicInteger();
    031
    032    /** The object used for synchronization on the send queue. */
    033    private final Object sendQueueLock;
    034    private final ILogAgent field_98215_i;
    035
    036    /** The socket used by this network manager. */
    037    private Socket networkSocket;
    038
    039    /** The InetSocketAddress of the remote endpoint */
    040    private final SocketAddress remoteSocketAddress;
    041
    042    /** The input stream connected to the socket. */
    043    private volatile DataInputStream socketInputStream;
    044
    045    /** The output stream connected to the socket. */
    046    private volatile DataOutputStream socketOutputStream;
    047
    048    /** Whether the network is currently operational. */
    049    private volatile boolean isRunning;
    050
    051    /**
    052     * Whether this network manager is currently terminating (and should ignore further errors).
    053     */
    054    private volatile boolean isTerminating;
    055
    056    /**
    057     * Linked list of packets that have been read and are awaiting processing.
    058     */
    059    private List readPackets;
    060
    061    /** Linked list of packets awaiting sending. */
    062    private List dataPackets;
    063
    064    /** Linked list of packets with chunk data that are awaiting sending. */
    065    private List chunkDataPackets;
    066
    067    /** A reference to the NetHandler object. */
    068    private NetHandler theNetHandler;
    069
    070    /**
    071     * Whether this server is currently terminating. If this is a client, this is always false.
    072     */
    073    private boolean isServerTerminating;
    074
    075    /** The thread used for writing. */
    076    private Thread writeThread;
    077
    078    /** The thread used for reading. */
    079    private Thread readThread;
    080
    081    /** A String indicating why the network has shutdown. */
    082    private String terminationReason;
    083    private Object[] field_74480_w;
    084    private int field_74490_x;
    085
    086    /**
    087     * The length in bytes of the packets in both send queues (data and chunkData).
    088     */
    089    private int sendQueueByteLength;
    090    public static int[] field_74470_c = new int[256];
    091    public static int[] field_74467_d = new int[256];
    092    public int field_74468_e;
    093    boolean isInputBeingDecrypted;
    094    boolean isOutputEncrypted;
    095    private SecretKey sharedKeyForEncryption;
    096    private PrivateKey field_74463_A;
    097
    098    /**
    099     * Delay for sending pending chunk data packets (as opposed to pending non-chunk data packets)
    100     */
    101    private int chunkDataPacketsDelay;
    102
    103    @SideOnly(Side.CLIENT)
    104    public TcpConnection(ILogAgent par1ILogAgent, Socket par2Socket, String par3Str, NetHandler par4NetHandler) throws IOException
    105    {
    106        this(par1ILogAgent, par2Socket, par3Str, par4NetHandler, (PrivateKey)null);
    107    }
    108
    109    public TcpConnection(ILogAgent par1ILogAgent, Socket par2Socket, String par3Str, NetHandler par4NetHandler, PrivateKey par5PrivateKey) throws IOException
    110    {
    111        this.sendQueueLock = new Object();
    112        this.isRunning = true;
    113        this.isTerminating = false;
    114        this.readPackets = Collections.synchronizedList(new ArrayList());
    115        this.dataPackets = Collections.synchronizedList(new ArrayList());
    116        this.chunkDataPackets = Collections.synchronizedList(new ArrayList());
    117        this.isServerTerminating = false;
    118        this.terminationReason = "";
    119        this.field_74490_x = 0;
    120        this.sendQueueByteLength = 0;
    121        this.field_74468_e = 0;
    122        this.isInputBeingDecrypted = false;
    123        this.isOutputEncrypted = false;
    124        this.sharedKeyForEncryption = null;
    125        this.field_74463_A = null;
    126        this.chunkDataPacketsDelay = 50;
    127        this.field_74463_A = par5PrivateKey;
    128        this.networkSocket = par2Socket;
    129        this.field_98215_i = par1ILogAgent;
    130        this.remoteSocketAddress = par2Socket.getRemoteSocketAddress();
    131        this.theNetHandler = par4NetHandler;
    132
    133        try
    134        {
    135            par2Socket.setSoTimeout(30000);
    136            par2Socket.setTrafficClass(24);
    137        }
    138        catch (SocketException socketexception)
    139        {
    140            System.err.println(socketexception.getMessage());
    141        }
    142
    143        this.socketInputStream = new DataInputStream(par2Socket.getInputStream());
    144        this.socketOutputStream = new DataOutputStream(new BufferedOutputStream(par2Socket.getOutputStream(), 5120));
    145        this.readThread = new TcpReaderThread(this, par3Str + " read thread");
    146        this.writeThread = new TcpWriterThread(this, par3Str + " write thread");
    147        this.readThread.start();
    148        this.writeThread.start();
    149    }
    150
    151    @SideOnly(Side.CLIENT)
    152    public void closeConnections()
    153    {
    154        this.wakeThreads();
    155        this.writeThread = null;
    156        this.readThread = null;
    157    }
    158
    159    /**
    160     * Sets the NetHandler for this NetworkManager. Server-only.
    161     */
    162    public void setNetHandler(NetHandler par1NetHandler)
    163    {
    164        this.theNetHandler = par1NetHandler;
    165    }
    166
    167    /**
    168     * Adds the packet to the correct send queue (chunk data packets go to a separate queue).
    169     */
    170    public void addToSendQueue(Packet par1Packet)
    171    {
    172        if (!this.isServerTerminating)
    173        {
    174            Object object = this.sendQueueLock;
    175
    176            synchronized (this.sendQueueLock)
    177            {
    178                this.sendQueueByteLength += par1Packet.getPacketSize() + 1;
    179                this.dataPackets.add(par1Packet);
    180            }
    181        }
    182    }
    183
    184    /**
    185     * Sends a data packet if there is one to send, or sends a chunk data packet if there is one and the counter is up,
    186     * or does nothing.
    187     */
    188    private boolean sendPacket()
    189    {
    190        boolean flag = false;
    191
    192        try
    193        {
    194            Packet packet;
    195            int i;
    196            int[] aint;
    197
    198            if (this.field_74468_e == 0 || !this.dataPackets.isEmpty() && System.currentTimeMillis() - ((Packet)this.dataPackets.get(0)).creationTimeMillis >= (long)this.field_74468_e)
    199            {
    200                packet = this.func_74460_a(false);
    201
    202                if (packet != null)
    203                {
    204                    Packet.writePacket(packet, this.socketOutputStream);
    205
    206                    if (packet instanceof Packet252SharedKey && !this.isOutputEncrypted)
    207                    {
    208                        if (!this.theNetHandler.isServerHandler())
    209                        {
    210                            this.sharedKeyForEncryption = ((Packet252SharedKey)packet).getSharedKey();
    211                        }
    212
    213                        this.encryptOuputStream();
    214                    }
    215
    216                    aint = field_74467_d;
    217                    i = packet.getPacketId();
    218                    aint[i] += packet.getPacketSize() + 1;
    219                    flag = true;
    220                }
    221            }
    222
    223            if (this.chunkDataPacketsDelay-- <= 0 && (this.field_74468_e == 0 || !this.chunkDataPackets.isEmpty() && System.currentTimeMillis() - ((Packet)this.chunkDataPackets.get(0)).creationTimeMillis >= (long)this.field_74468_e))
    224            {
    225                packet = this.func_74460_a(true);
    226
    227                if (packet != null)
    228                {
    229                    Packet.writePacket(packet, this.socketOutputStream);
    230                    aint = field_74467_d;
    231                    i = packet.getPacketId();
    232                    aint[i] += packet.getPacketSize() + 1;
    233                    this.chunkDataPacketsDelay = 0;
    234                    flag = true;
    235                }
    236            }
    237
    238            return flag;
    239        }
    240        catch (Exception exception)
    241        {
    242            if (!this.isTerminating)
    243            {
    244                this.onNetworkError(exception);
    245            }
    246
    247            return false;
    248        }
    249    }
    250
    251    private Packet func_74460_a(boolean par1)
    252    {
    253        Packet packet = null;
    254        List list = par1 ? this.chunkDataPackets : this.dataPackets;
    255        Object object = this.sendQueueLock;
    256
    257        synchronized (this.sendQueueLock)
    258        {
    259            while (!list.isEmpty() && packet == null)
    260            {
    261                packet = (Packet)list.remove(0);
    262                this.sendQueueByteLength -= packet.getPacketSize() + 1;
    263
    264                if (this.func_74454_a(packet, par1))
    265                {
    266                    packet = null;
    267                }
    268            }
    269
    270            return packet;
    271        }
    272    }
    273
    274    private boolean func_74454_a(Packet par1Packet, boolean par2)
    275    {
    276        if (!par1Packet.isRealPacket())
    277        {
    278            return false;
    279        }
    280        else
    281        {
    282            List list = par2 ? this.chunkDataPackets : this.dataPackets;
    283            Iterator iterator = list.iterator();
    284            Packet packet1;
    285
    286            do
    287            {
    288                if (!iterator.hasNext())
    289                {
    290                    return false;
    291                }
    292
    293                packet1 = (Packet)iterator.next();
    294            }
    295            while (packet1.getPacketId() != par1Packet.getPacketId());
    296
    297            return par1Packet.containsSameEntityIDAs(packet1);
    298        }
    299    }
    300
    301    /**
    302     * Wakes reader and writer threads
    303     */
    304    public void wakeThreads()
    305    {
    306        if (this.readThread != null)
    307        {
    308            this.readThread.interrupt();
    309        }
    310
    311        if (this.writeThread != null)
    312        {
    313            this.writeThread.interrupt();
    314        }
    315    }
    316
    317    /**
    318     * Reads a single packet from the input stream and adds it to the read queue. If no packet is read, it shuts down
    319     * the network.
    320     */
    321    private boolean readPacket()
    322    {
    323        boolean flag = false;
    324
    325        try
    326        {
    327            Packet packet = Packet.readPacket(this.field_98215_i, this.socketInputStream, this.theNetHandler.isServerHandler(), this.networkSocket);
    328
    329            if (packet != null)
    330            {
    331                if (packet instanceof Packet252SharedKey && !this.isInputBeingDecrypted)
    332                {
    333                    if (this.theNetHandler.isServerHandler())
    334                    {
    335                        this.sharedKeyForEncryption = ((Packet252SharedKey)packet).getSharedKey(this.field_74463_A);
    336                    }
    337
    338                    this.decryptInputStream();
    339                }
    340
    341                int[] aint = field_74470_c;
    342                int i = packet.getPacketId();
    343                aint[i] += packet.getPacketSize() + 1;
    344
    345                if (!this.isServerTerminating)
    346                {
    347                    if (packet.canProcessAsync() && this.theNetHandler.canProcessPacketsAsync())
    348                    {
    349                        this.field_74490_x = 0;
    350                        packet.processPacket(this.theNetHandler);
    351                    }
    352                    else
    353                    {
    354                        this.readPackets.add(packet);
    355                    }
    356                }
    357
    358                flag = true;
    359            }
    360            else
    361            {
    362                this.networkShutdown("disconnect.endOfStream", new Object[0]);
    363            }
    364
    365            return flag;
    366        }
    367        catch (Exception exception)
    368        {
    369            if (!this.isTerminating)
    370            {
    371                this.onNetworkError(exception);
    372            }
    373
    374            return false;
    375        }
    376    }
    377
    378    /**
    379     * Used to report network errors and causes a network shutdown.
    380     */
    381    private void onNetworkError(Exception par1Exception)
    382    {
    383        par1Exception.printStackTrace();
    384        this.networkShutdown("disconnect.genericReason", new Object[] {"Internal exception: " + par1Exception.toString()});
    385    }
    386
    387    /**
    388     * Shuts down the network with the specified reason. Closes all streams and sockets, spawns NetworkMasterThread to
    389     * stop reading and writing threads.
    390     */
    391    public void networkShutdown(String par1Str, Object ... par2ArrayOfObj)
    392    {
    393        if (this.isRunning)
    394        {
    395            this.isTerminating = true;
    396            this.terminationReason = par1Str;
    397            this.field_74480_w = par2ArrayOfObj;
    398            this.isRunning = false;
    399            (new TcpMasterThread(this)).start();
    400
    401            try
    402            {
    403                this.socketInputStream.close();
    404            }
    405            catch (Throwable throwable)
    406            {
    407                ;
    408            }
    409
    410            try
    411            {
    412                this.socketOutputStream.close();
    413            }
    414            catch (Throwable throwable1)
    415            {
    416                ;
    417            }
    418
    419            try
    420            {
    421                this.networkSocket.close();
    422            }
    423            catch (Throwable throwable2)
    424            {
    425                ;
    426            }
    427
    428            this.socketInputStream = null;
    429            this.socketOutputStream = null;
    430            this.networkSocket = null;
    431        }
    432    }
    433
    434    /**
    435     * Checks timeouts and processes all pending read packets.
    436     */
    437    public void processReadPackets()
    438    {
    439        if (this.sendQueueByteLength > 2097152)
    440        {
    441            this.networkShutdown("disconnect.overflow", new Object[0]);
    442        }
    443
    444        if (this.readPackets.isEmpty())
    445        {
    446            if (this.field_74490_x++ == 1200)
    447            {
    448                this.networkShutdown("disconnect.timeout", new Object[0]);
    449            }
    450        }
    451        else
    452        {
    453            this.field_74490_x = 0;
    454        }
    455
    456        int i = 1000;
    457
    458        while (!this.readPackets.isEmpty() && i-- >= 0)
    459        {
    460            Packet packet = (Packet)this.readPackets.remove(0);
    461            packet.processPacket(this.theNetHandler);
    462        }
    463
    464        this.wakeThreads();
    465
    466        if (this.isTerminating && this.readPackets.isEmpty())
    467        {
    468            this.theNetHandler.handleErrorMessage(this.terminationReason, this.field_74480_w);
    469            FMLNetworkHandler.onConnectionClosed(this, this.theNetHandler.getPlayer());
    470        }
    471    }
    472
    473    /**
    474     * Return the InetSocketAddress of the remote endpoint
    475     */
    476    public SocketAddress getSocketAddress()
    477    {
    478        return this.remoteSocketAddress;
    479    }
    480
    481    /**
    482     * Shuts down the server. (Only actually used on the server)
    483     */
    484    public void serverShutdown()
    485    {
    486        if (!this.isServerTerminating)
    487        {
    488            this.wakeThreads();
    489            this.isServerTerminating = true;
    490            this.readThread.interrupt();
    491            (new TcpMonitorThread(this)).start();
    492        }
    493    }
    494
    495    private void decryptInputStream() throws IOException
    496    {
    497        this.isInputBeingDecrypted = true;
    498        InputStream inputstream = this.networkSocket.getInputStream();
    499        this.socketInputStream = new DataInputStream(CryptManager.decryptInputStream(this.sharedKeyForEncryption, inputstream));
    500    }
    501
    502    /**
    503     * flushes the stream and replaces it with an encryptedOutputStream
    504     */
    505    private void encryptOuputStream() throws IOException
    506    {
    507        this.socketOutputStream.flush();
    508        this.isOutputEncrypted = true;
    509        BufferedOutputStream bufferedoutputstream = new BufferedOutputStream(CryptManager.encryptOuputStream(this.sharedKeyForEncryption, this.networkSocket.getOutputStream()), 5120);
    510        this.socketOutputStream = new DataOutputStream(bufferedoutputstream);
    511    }
    512
    513    /**
    514     * returns 0 for memoryConnections
    515     */
    516    public int packetSize()
    517    {
    518        return this.chunkDataPackets.size();
    519    }
    520
    521    public Socket getSocket()
    522    {
    523        return this.networkSocket;
    524    }
    525
    526    /**
    527     * Whether the network is operational.
    528     */
    529    static boolean isRunning(TcpConnection par0TcpConnection)
    530    {
    531        return par0TcpConnection.isRunning;
    532    }
    533
    534    /**
    535     * Is the server terminating? Client side aways returns false.
    536     */
    537    static boolean isServerTerminating(TcpConnection par0TcpConnection)
    538    {
    539        return par0TcpConnection.isServerTerminating;
    540    }
    541
    542    /**
    543     * Static accessor to readPacket.
    544     */
    545    static boolean readNetworkPacket(TcpConnection par0TcpConnection)
    546    {
    547        return par0TcpConnection.readPacket();
    548    }
    549
    550    /**
    551     * Static accessor to sendPacket.
    552     */
    553    static boolean sendNetworkPacket(TcpConnection par0TcpConnection)
    554    {
    555        return par0TcpConnection.sendPacket();
    556    }
    557
    558    static DataOutputStream getOutputStream(TcpConnection par0TcpConnection)
    559    {
    560        return par0TcpConnection.socketOutputStream;
    561    }
    562
    563    /**
    564     * Gets whether the Network manager is terminating.
    565     */
    566    static boolean isTerminating(TcpConnection par0TcpConnection)
    567    {
    568        return par0TcpConnection.isTerminating;
    569    }
    570
    571    /**
    572     * Sends the network manager an error
    573     */
    574    static void sendError(TcpConnection par0TcpConnection, Exception par1Exception)
    575    {
    576        par0TcpConnection.onNetworkError(par1Exception);
    577    }
    578
    579    /**
    580     * Returns the read thread.
    581     */
    582    static Thread getReadThread(TcpConnection par0TcpConnection)
    583    {
    584        return par0TcpConnection.readThread;
    585    }
    586
    587    /**
    588     * Returns the write thread.
    589     */
    590    static Thread getWriteThread(TcpConnection par0TcpConnection)
    591    {
    592        return par0TcpConnection.writeThread;
    593    }
    594}