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