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