package electrosphere.net.server; import electrosphere.entity.types.creature.CreatureTemplate; import electrosphere.entity.types.creature.CreatureUtils; import electrosphere.engine.Globals; import electrosphere.engine.Main; import electrosphere.entity.Entity; import electrosphere.entity.EntityUtils; import electrosphere.entity.state.ironsight.IronSightTree; import electrosphere.entity.types.item.ItemUtils; import electrosphere.entity.types.attach.AttachUtils; import electrosphere.entity.types.collision.CollisionObjUtils; import electrosphere.logger.LoggerInterface; import electrosphere.net.NetUtils; import electrosphere.net.parser.net.message.AuthMessage; import electrosphere.net.parser.net.message.EntityMessage; import electrosphere.net.parser.net.message.NetworkMessage; import electrosphere.net.parser.net.message.PlayerMessage; import electrosphere.net.parser.net.message.ServerMessage; import electrosphere.net.parser.net.raw.NetworkParser; import electrosphere.net.server.player.Player; import electrosphere.net.server.protocol.ServerProtocol; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.math.BigInteger; import java.net.Socket; import java.net.SocketException; import java.nio.ByteBuffer; import java.security.spec.RSAKeyGenParameterSpec; import java.util.Properties; import java.util.Random; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import javax.crypto.spec.SecretKeySpec; import org.joml.Vector3d; import org.joml.Vector3f; /** * * @author satellite */ public class ServerConnectionHandler implements Runnable { static int playerIdIncrementer = 0; //local carrier variables boolean local = false; //socket carrier variables Socket socket; // CryptoInputStream inputStream; // CryptoOutputStream outputStream; InputStream inputStream; OutputStream outputStream; boolean initialized; boolean isAuthenticated = false; NetworkParser networkParser; int playerID; int playerCharacterID; CreatureTemplate currentCreatureTemplate; ServerProtocol serverProtocol; //thresholds for determining when to send pings and when a client has disconnected static final long SEND_PING_THRESHOLD = 3000; static final long PING_DISCONNECT_THRESHOLD = 20000; //used to keep track of ping/pong messages with client long lastPingTime = 0; long lastPongTime = 0; //flag to disconnect due to pipe break boolean socketException = false; public ServerConnectionHandler(Socket socket) { this.socket = socket; playerID = getNewPlayerID(); LoggerInterface.loggerNetworking.INFO("Player ID: " + playerID); } public ServerConnectionHandler(InputStream serverInputStream, OutputStream serverOutputStream){ this.local = true; playerID = getNewPlayerID(); LoggerInterface.loggerNetworking.INFO("Player ID: " + playerID); inputStream = serverInputStream; outputStream = serverOutputStream; } @Override public void run() { LoggerInterface.loggerNetworking.INFO("ServerConnectionHandler start"); initialized = false; //startup is different whether need to set socket streams or received in construction of object (ie if creating local "connection") if(this.local){ //run if serverconnectionHandler is created by passing in input/output streams networkParser = new NetworkParser(inputStream,outputStream); serverProtocol = new ServerProtocol(this); } else { //run if ServerConnectionHandler is created by passing in a socket try { socket.setSoTimeout(100); } catch (SocketException ex) { ex.printStackTrace(); } // final SecretKeySpec key = new SecretKeySpec(("1234567890123456").getBytes(),"AES"); // final Properties properties = new Properties(); // final RSAKeyGenParameterSpec spec = new RSAKeyGenParameterSpec(4096, BigInteger.probablePrime(4000, new Random())); // try { // inputStream = new CryptoInputStream("AES/ECB/PKCS5Padding",properties,socket.getInputStream(),key,spec); // } catch (IOException ex) { // ex.printStackTrace(); // System.exit(1); // } // try { // outputStream = new CryptoOutputStream("AES/ECB/PKCS5Padding",properties,socket.getOutputStream(),key,spec); // } catch (IOException ex) { // ex.printStackTrace(); // System.exit(1); // } try { inputStream = socket.getInputStream(); outputStream = socket.getOutputStream(); networkParser = new NetworkParser(inputStream,outputStream); serverProtocol = new ServerProtocol(this); } catch (IOException ex) { ex.printStackTrace(); System.exit(1); } } networkParser.addOutgoingMessage(ServerMessage.constructPingMessage()); networkParser.addOutgoingMessage(AuthMessage.constructAuthRequestMessage()); initialized = true; while(Main.isRunning()){ //parse messages both incoming and outgoing try { parseMessages(); } catch (SocketException e) { //if we get a SocketException broken pipe (basically the client dc'd without telling us) //set flag to disconnect client //TODO: fix, this doesn't actually catch the socket exception which is exceedingly obnoxious socketException = true; LoggerInterface.loggerNetworking.DEBUG(e.getLocalizedMessage()); } //ping logic long currentTime = System.currentTimeMillis(); //basically if we haven't sent a ping in a while, send one if(currentTime - lastPingTime > SEND_PING_THRESHOLD){ addMessagetoOutgoingQueue(ServerMessage.constructPingMessage()); lastPingTime = currentTime; if(lastPongTime == 0){ lastPongTime = lastPingTime; } } //check if we meet disconnection criteria //has it been too long since the last ping? //have we had a socket exception? if(lastPingTime - lastPongTime > PING_DISCONNECT_THRESHOLD || this.socketException == true){ //disconnected from the server LoggerInterface.loggerNetworking.WARNING("Client disconnected"); //run disconnect routine disconnect(); break; } } } /** * Had to wrap the message parsing block in a function to throw a SocketException * without my linter freaking out * @throws SocketException */ void parseMessages() throws SocketException { //attempt poll incoming messages networkParser.readMessagesIn(); //ponder incoming messages while(networkParser.hasIncomingMessaage()){ NetworkMessage message = networkParser.popIncomingMessage(); serverProtocol.handleMessage(message); } //push outgoing message networkParser.pushMessagesOut(); try { //sleep TimeUnit.MILLISECONDS.sleep(1); } catch (InterruptedException ex) { ex.printStackTrace(); } } public void setPlayerId(int id){ playerID = id; } public int getPlayerId(){ return playerID; } static int getNewPlayerID(){ playerIdIncrementer++; return playerIdIncrementer; } public void setPlayerCharacterId(int id){ playerCharacterID = id; } public int getPlayerCharacterId(){ return playerCharacterID; } public String getIPAddress(){ if(local){ return "127.0.0.1"; } else { return socket.getRemoteSocketAddress().toString(); } } public void addMessagetoOutgoingQueue(NetworkMessage message){ switch(message.getType()){ case ENTITY_MESSAGE: switch(((EntityMessage)message).getMessageSubtype()){ case MOVEUPDATE: //we don't want this to fire if it's the server client because other people will keep running indefinitely //as the server broadcasts to itself that they're moving case ATTACKUPDATE: //we don't want this to fire if it's the server client because other people will keep running indefinitely //as the server broadcasts to itself that they're moving case SPAWNCREATURE: if(isServerClient()){ //basically don't send the message if this is the player that is also hosting the game } else { networkParser.addOutgoingMessage(message); } break; default: networkParser.addOutgoingMessage(message); break; } break; case INVENTORY_MESSAGE: if(isServerClient()){ //basically don't send the message if this is the player that is also hosting the game } else { networkParser.addOutgoingMessage(message); } break; default: networkParser.addOutgoingMessage(message); break; } } boolean isConnectionPlayerEntity(int id){ return id == this.playerCharacterID; } /** * Returns true if running both client and server in same instance of the app and this is the player for that instance of the app * @return */ boolean isServerClient(){ return Globals.RUN_SERVER && Globals.RUN_CLIENT && Globals.clientPlayer != null && this.playerID == Globals.clientPlayer.getId(); } public void setCreatureTemplate(CreatureTemplate currentCreatureTemplate){ this.currentCreatureTemplate = currentCreatureTemplate; } public CreatureTemplate getCurrentCreatureTemplate(){ return this.currentCreatureTemplate; } public void markReceivedPongMessage(){ lastPongTime = System.currentTimeMillis(); } /** * Routine to run when the client disconnects */ void disconnect(){ //close socket if(socket.isConnected()){ try { socket.close(); } catch (IOException e) { LoggerInterface.loggerNetworking.ERROR("Error closing socket", e); } } //figure out what player we are Player playerObject = Globals.playerManager.getPlayerFromId(getPlayerId()); //get player entity & position Entity playerEntity = playerObject.getPlayerEntity(); Vector3d position = EntityUtils.getPosition(playerEntity); //deregister entity Globals.entityManager.deregisterEntity(playerObject.getPlayerEntity()); //tell all clients to destroy the entity Globals.dataCellManager.getDataCellAtPoint(position).broadcastNetworkMessage(EntityMessage.constructDestroyMessage(playerEntity.getId())); } }