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