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