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