001package net.minecraft.network;
002
003import cpw.mods.fml.common.network.FMLNetworkHandler;
004import cpw.mods.fml.relauncher.Side;
005import cpw.mods.fml.relauncher.SideOnly;
006import java.io.BufferedOutputStream;
007import java.io.DataInputStream;
008import java.io.DataOutputStream;
009import java.io.IOException;
010import java.io.InputStream;
011import java.net.Socket;
012import java.net.SocketAddress;
013import java.net.SocketException;
014import java.security.PrivateKey;
015import java.util.ArrayList;
016import java.util.Collections;
017import java.util.Iterator;
018import java.util.List;
019import java.util.concurrent.atomic.AtomicInteger;
020import javax.crypto.SecretKey;
021import net.minecraft.network.packet.NetHandler;
022import net.minecraft.network.packet.Packet;
023import net.minecraft.network.packet.Packet252SharedKey;
024import net.minecraft.util.CryptManager;
025
026public 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}