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    }