package net.minecraft.network;
import cpw.mods.fml.common.network.FMLNetworkHandler;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.security.PrivateKey;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import javax.crypto.SecretKey;
import net.minecraft.network.packet.NetHandler;
import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.Packet252SharedKey;
import net.minecraft.util.CryptManager;
public class TcpConnection implements INetworkManager
{
public static AtomicInteger field_74471_a = new AtomicInteger();
public static AtomicInteger field_74469_b = new AtomicInteger();
/** The object used for synchronization on the send queue. */
private Object sendQueueLock;
/** The socket used by this network manager. */
private Socket networkSocket;
/** The InetSocketAddress of the remote endpoint */
private final SocketAddress remoteSocketAddress;
/** The input stream connected to the socket. */
private volatile DataInputStream socketInputStream;
/** The output stream connected to the socket. */
private volatile DataOutputStream socketOutputStream;
/** Whether the network is currently operational. */
private volatile boolean isRunning;
/**
* Whether this network manager is currently terminating (and should ignore further errors).
*/
private volatile boolean isTerminating;
/**
* Linked list of packets that have been read and are awaiting processing.
*/
private List readPackets;
/** Linked list of packets awaiting sending. */
private List dataPackets;
/** Linked list of packets with chunk data that are awaiting sending. */
private List chunkDataPackets;
/** A reference to the NetHandler object. */
private NetHandler theNetHandler;
/**
* Whether this server is currently terminating. If this is a client, this is always false.
*/
private boolean isServerTerminating;
/** The thread used for writing. */
private Thread writeThread;
/** The thread used for reading. */
private Thread readThread;
/** A String indicating why the network has shutdown. */
private String terminationReason;
private Object[] field_74480_w;
private int field_74490_x;
/**
* The length in bytes of the packets in both send queues (data and chunkData).
*/
private int sendQueueByteLength;
public static int[] field_74470_c = new int[256];
public static int[] field_74467_d = new int[256];
public int field_74468_e;
boolean isInputBeingDecrypted;
boolean isOutputEncrypted;
private SecretKey sharedKeyForEncryption;
private PrivateKey field_74463_A;
/**
* Delay for sending pending chunk data packets (as opposed to pending non-chunk data packets)
*/
private int chunkDataPacketsDelay;
@SideOnly(Side.CLIENT)
public TcpConnection(Socket par1Socket, String par2Str, NetHandler par3NetHandler) throws IOException
{
this(par1Socket, par2Str, par3NetHandler, (PrivateKey)null);
}
public TcpConnection(Socket par1Socket, String par2Str, NetHandler par3NetHandler, PrivateKey par4PrivateKey) throws IOException
{
this.sendQueueLock = new Object();
this.isRunning = true;
this.isTerminating = false;
this.readPackets = Collections.synchronizedList(new ArrayList());
this.dataPackets = Collections.synchronizedList(new ArrayList());
this.chunkDataPackets = Collections.synchronizedList(new ArrayList());
this.isServerTerminating = false;
this.terminationReason = "";
this.field_74490_x = 0;
this.sendQueueByteLength = 0;
this.field_74468_e = 0;
this.isInputBeingDecrypted = false;
this.isOutputEncrypted = false;
this.sharedKeyForEncryption = null;
this.field_74463_A = null;
this.chunkDataPacketsDelay = 50;
this.field_74463_A = par4PrivateKey;
this.networkSocket = par1Socket;
this.remoteSocketAddress = par1Socket.getRemoteSocketAddress();
this.theNetHandler = par3NetHandler;
try
{
par1Socket.setSoTimeout(30000);
par1Socket.setTrafficClass(24);
}
catch (SocketException var6)
{
System.err.println(var6.getMessage());
}
this.socketInputStream = new DataInputStream(par1Socket.getInputStream());
this.socketOutputStream = new DataOutputStream(new BufferedOutputStream(par1Socket.getOutputStream(), 5120));
this.readThread = new TcpReaderThread(this, par2Str + " read thread");
this.writeThread = new TcpWriterThread(this, par2Str + " write thread");
this.readThread.start();
this.writeThread.start();
}
@SideOnly(Side.CLIENT)
public void closeConnections()
{
this.wakeThreads();
this.writeThread = null;
this.readThread = null;
}
/**
* Sets the NetHandler for this NetworkManager. Server-only.
*/
public void setNetHandler(NetHandler par1NetHandler)
{
this.theNetHandler = par1NetHandler;
}
/**
* Adds the packet to the correct send queue (chunk data packets go to a separate queue).
*/
public void addToSendQueue(Packet par1Packet)
{
if (!this.isServerTerminating)
{
Object var2 = this.sendQueueLock;
synchronized (this.sendQueueLock)
{
this.sendQueueByteLength += par1Packet.getPacketSize() + 1;
this.dataPackets.add(par1Packet);
}
}
}
/**
* 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,
* or does nothing.
*/
private boolean sendPacket()
{
boolean var1 = false;
try
{
Packet var2;
int var10001;
int[] var10000;
if (this.field_74468_e == 0 || !this.dataPackets.isEmpty() && System.currentTimeMillis() - ((Packet)this.dataPackets.get(0)).creationTimeMillis >= (long)this.field_74468_e)
{
var2 = this.func_74460_a(false);
if (var2 != null)
{
Packet.writePacket(var2, this.socketOutputStream);
if (var2 instanceof Packet252SharedKey && !this.isOutputEncrypted)
{
if (!this.theNetHandler.isServerHandler())
{
this.sharedKeyForEncryption = ((Packet252SharedKey)var2).getSharedKey();
}
this.encryptOuputStream();
}
var10000 = field_74467_d;
var10001 = var2.getPacketId();
var10000[var10001] += var2.getPacketSize() + 1;
var1 = true;
}
}
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))
{
var2 = this.func_74460_a(true);
if (var2 != null)
{
Packet.writePacket(var2, this.socketOutputStream);
var10000 = field_74467_d;
var10001 = var2.getPacketId();
var10000[var10001] += var2.getPacketSize() + 1;
this.chunkDataPacketsDelay = 0;
var1 = true;
}
}
return var1;
}
catch (Exception var3)
{
if (!this.isTerminating)
{
this.onNetworkError(var3);
}
return false;
}
}
private Packet func_74460_a(boolean par1)
{
Packet var2 = null;
List var3 = par1 ? this.chunkDataPackets : this.dataPackets;
Object var4 = this.sendQueueLock;
synchronized (this.sendQueueLock)
{
while (!var3.isEmpty() && var2 == null)
{
var2 = (Packet)var3.remove(0);
this.sendQueueByteLength -= var2.getPacketSize() + 1;
if (this.func_74454_a(var2, par1))
{
var2 = null;
}
}
return var2;
}
}
private boolean func_74454_a(Packet par1Packet, boolean par2)
{
if (!par1Packet.isRealPacket())
{
return false;
}
else
{
List var3 = par2 ? this.chunkDataPackets : this.dataPackets;
Iterator var4 = var3.iterator();
Packet var5;
do
{
if (!var4.hasNext())
{
return false;
}
var5 = (Packet)var4.next();
}
while (var5.getPacketId() != par1Packet.getPacketId());
return par1Packet.containsSameEntityIDAs(var5);
}
}
/**
* Wakes reader and writer threads
*/
public void wakeThreads()
{
if (this.readThread != null)
{
this.readThread.interrupt();
}
if (this.writeThread != null)
{
this.writeThread.interrupt();
}
}
/**
* Reads a single packet from the input stream and adds it to the read queue. If no packet is read, it shuts down
* the network.
*/
private boolean readPacket()
{
boolean var1 = false;
try
{
Packet var2 = Packet.readPacket(this.socketInputStream, this.theNetHandler.isServerHandler(), this.networkSocket);
if (var2 != null)
{
if (var2 instanceof Packet252SharedKey && !this.isInputBeingDecrypted)
{
if (this.theNetHandler.isServerHandler())
{
this.sharedKeyForEncryption = ((Packet252SharedKey)var2).getSharedKey(this.field_74463_A);
}
this.decryptInputStream();
}
int[] var10000 = field_74470_c;
int var10001 = var2.getPacketId();
var10000[var10001] += var2.getPacketSize() + 1;
if (!this.isServerTerminating)
{
if (var2.canProcessAsync() && this.theNetHandler.canProcessPacketsAsync())
{
this.field_74490_x = 0;
var2.processPacket(this.theNetHandler);
}
else
{
this.readPackets.add(var2);
}
}
var1 = true;
}
else
{
this.networkShutdown("disconnect.endOfStream", new Object[0]);
}
return var1;
}
catch (Exception var3)
{
if (!this.isTerminating)
{
this.onNetworkError(var3);
}
return false;
}
}
/**
* Used to report network errors and causes a network shutdown.
*/
private void onNetworkError(Exception par1Exception)
{
par1Exception.printStackTrace();
this.networkShutdown("disconnect.genericReason", new Object[] {"Internal exception: " + par1Exception.toString()});
}
/**
* Shuts down the network with the specified reason. Closes all streams and sockets, spawns NetworkMasterThread to
* stop reading and writing threads.
*/
public void networkShutdown(String par1Str, Object ... par2ArrayOfObj)
{
if (this.isRunning)
{
this.isTerminating = true;
this.terminationReason = par1Str;
this.field_74480_w = par2ArrayOfObj;
this.isRunning = false;
(new TcpMasterThread(this)).start();
try
{
this.socketInputStream.close();
}
catch (Throwable var6)
{
;
}
try
{
this.socketOutputStream.close();
}
catch (Throwable var5)
{
;
}
try
{
this.networkSocket.close();
}
catch (Throwable var4)
{
;
}
this.socketInputStream = null;
this.socketOutputStream = null;
this.networkSocket = null;
}
}
/**
* Checks timeouts and processes all pending read packets.
*/
public void processReadPackets()
{
if (this.sendQueueByteLength > 2097152)
{
this.networkShutdown("disconnect.overflow", new Object[0]);
}
if (this.readPackets.isEmpty())
{
if (this.field_74490_x++ == 1200)
{
this.networkShutdown("disconnect.timeout", new Object[0]);
}
}
else
{
this.field_74490_x = 0;
}
int var1 = 1000;
while (!this.readPackets.isEmpty() && var1-- >= 0)
{
Packet var2 = (Packet)this.readPackets.remove(0);
var2.processPacket(this.theNetHandler);
}
this.wakeThreads();
if (this.isTerminating && this.readPackets.isEmpty())
{
this.theNetHandler.handleErrorMessage(this.terminationReason, this.field_74480_w);
FMLNetworkHandler.onConnectionClosed(this, this.theNetHandler.getPlayer());
}
}
/**
* Return the InetSocketAddress of the remote endpoint
*/
public SocketAddress getSocketAddress()
{
return this.remoteSocketAddress;
}
/**
* Shuts down the server. (Only actually used on the server)
*/
public void serverShutdown()
{
if (!this.isServerTerminating)
{
this.wakeThreads();
this.isServerTerminating = true;
this.readThread.interrupt();
(new TcpMonitorThread(this)).start();
}
}
private void decryptInputStream() throws IOException
{
this.isInputBeingDecrypted = true;
InputStream var1 = this.networkSocket.getInputStream();
this.socketInputStream = new DataInputStream(CryptManager.decryptInputStream(this.sharedKeyForEncryption, var1));
}
/**
* flushes the stream and replaces it with an encryptedOutputStream
*/
private void encryptOuputStream() throws IOException
{
this.socketOutputStream.flush();
this.isOutputEncrypted = true;
BufferedOutputStream var1 = new BufferedOutputStream(CryptManager.encryptOuputStream(this.sharedKeyForEncryption, this.networkSocket.getOutputStream()), 5120);
this.socketOutputStream = new DataOutputStream(var1);
}
/**
* returns 0 for memoryConnections
*/
public int packetSize()
{
return this.chunkDataPackets.size();
}
public Socket getSocket()
{
return this.networkSocket;
}
/**
* Whether the network is operational.
*/
static boolean isRunning(TcpConnection par0TcpConnection)
{
return par0TcpConnection.isRunning;
}
/**
* Is the server terminating? Client side aways returns false.
*/
static boolean isServerTerminating(TcpConnection par0TcpConnection)
{
return par0TcpConnection.isServerTerminating;
}
/**
* Static accessor to readPacket.
*/
static boolean readNetworkPacket(TcpConnection par0TcpConnection)
{
return par0TcpConnection.readPacket();
}
/**
* Static accessor to sendPacket.
*/
static boolean sendNetworkPacket(TcpConnection par0TcpConnection)
{
return par0TcpConnection.sendPacket();
}
static DataOutputStream getOutputStream(TcpConnection par0TcpConnection)
{
return par0TcpConnection.socketOutputStream;
}
/**
* Gets whether the Network manager is terminating.
*/
static boolean isTerminating(TcpConnection par0TcpConnection)
{
return par0TcpConnection.isTerminating;
}
/**
* Sends the network manager an error
*/
static void sendError(TcpConnection par0TcpConnection, Exception par1Exception)
{
par0TcpConnection.onNetworkError(par1Exception);
}
/**
* Returns the read thread.
*/
static Thread getReadThread(TcpConnection par0TcpConnection)
{
return par0TcpConnection.readThread;
}
/**
* Returns the write thread.
*/
static Thread getWriteThread(TcpConnection par0TcpConnection)
{
return par0TcpConnection.writeThread;
}
}