    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}