Renderer/src/main/java/electrosphere/net/server/ServerConnectionHandler.java
2022-10-10 20:51:23 -04:00

308 lines
11 KiB
Java

package electrosphere.net.server;
import electrosphere.entity.types.creature.CreatureTemplate;
import electrosphere.entity.types.creature.CreatureUtils;
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.main.Globals;
import electrosphere.main.Main;
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()));
}
}