feat: partially implemented net connection and handshake
This commit is contained in:
		| @ -3,30 +3,53 @@ package uulm.teamname.marvelous.server.netconnector; | ||||
| import org.java_websocket.WebSocket; | ||||
| import org.java_websocket.handshake.ClientHandshake; | ||||
| import org.java_websocket.server.WebSocketServer; | ||||
| import org.tinylog.Logger; | ||||
| import uulm.teamname.marvelous.gamelibrary.json.JSON; | ||||
|  | ||||
| import java.net.InetSocketAddress; | ||||
|  | ||||
| public class MarvelousServer extends WebSocketServer { | ||||
|     @Override | ||||
|     public void onOpen(WebSocket conn, ClientHandshake handshake) { | ||||
|  | ||||
|         Logger.info("New client connected. Adding new User."); | ||||
|         userManager.connectUser(conn, handshake); | ||||
|         // TODO: Send messages to UserManager, which then packages that stuff and sends it further | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onClose(WebSocket conn, int code, String reason, boolean remote) { | ||||
|  | ||||
|         Logger.info("Client disconnected"); | ||||
|         userManager.disconnectUser(conn, remote); | ||||
|         // TODO: Send messages to UserManager, which then removes the user and such | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onMessage(WebSocket conn, String message) { | ||||
|  | ||||
|         Logger.debug("Message received: {}", message); | ||||
|         userManager.messageReceived(conn, message); | ||||
|         // TODO: Send messages to UserManager, which then packages them and sends them further | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onError(WebSocket conn, Exception ex) { | ||||
|  | ||||
|         // TODO: Send errors to UserManager, which then answers with even more errors | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onStart() { | ||||
|         // TODO: Get the UserManager, and tell it that this thing is active | ||||
|     } | ||||
|  | ||||
|     private final UserManager userManager; | ||||
|  | ||||
|     public MarvelousServer(InetSocketAddress address, JSON json) { | ||||
|         super(address); | ||||
|         this.userManager = new UserManager(json); | ||||
|     } | ||||
|  | ||||
|     /** Practically No-Arg constructor for testing. <b>NEVER USE ANYWHERE ELSE!</b> | ||||
|      * @param userManager*/ | ||||
|     protected MarvelousServer(UserManager userManager) { super(); | ||||
|         this.userManager = userManager; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -0,0 +1,43 @@ | ||||
| package uulm.teamname.marvelous.server.netconnector; | ||||
|  | ||||
| import java.util.Objects; | ||||
|  | ||||
| /** The SUID (or Somewhat Unique ID) is the ID described by the Name and ID that the client provides on Connect */ | ||||
| public class SUID { | ||||
|     private final String name; | ||||
|     private final String deviceID; | ||||
|  | ||||
|     public SUID(String name, String deviceID) { | ||||
|         this.name = name; | ||||
|         this.deviceID = deviceID; | ||||
|     } | ||||
|  | ||||
|     public String getName() { | ||||
|         return name; | ||||
|     } | ||||
|  | ||||
|     public String getDeviceID() { | ||||
|         return deviceID; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean equals(Object o) { | ||||
|         if (this == o) return true; | ||||
|         if (o == null || getClass() != o.getClass()) return false; | ||||
|         SUID suid = (SUID) o; | ||||
|         return Objects.equals(name, suid.name) && Objects.equals(deviceID, suid.deviceID); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int hashCode() { | ||||
|         return Objects.hash(name, deviceID); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String toString() { | ||||
|         return "SUID{" + | ||||
|                 "name='" + name + '\'' + | ||||
|                 ", deviceID='" + deviceID + '\'' + | ||||
|                 '}'; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,155 @@ | ||||
| package uulm.teamname.marvelous.server.netconnector; | ||||
|  | ||||
| import org.java_websocket.handshake.ClientHandshake; | ||||
| import org.tinylog.Logger; | ||||
| import uulm.teamname.marvelous.gamelibrary.json.JSON; | ||||
| import uulm.teamname.marvelous.gamelibrary.messages.BasicMessage; | ||||
| import uulm.teamname.marvelous.gamelibrary.messages.ErrorMessage; | ||||
| import uulm.teamname.marvelous.gamelibrary.messages.client.HelloServerMessage; | ||||
| import uulm.teamname.marvelous.server.lobbymanager.Participant; | ||||
| import uulm.teamname.marvelous.server.netconnector.UserMetadata; | ||||
|  | ||||
| import org.java_websocket.WebSocket; | ||||
|  | ||||
| import java.util.*; | ||||
|  | ||||
| public class UserManager { | ||||
|  | ||||
|     /** A set of users that aren't assigned to lobbies or character selection yet, and their resource descriptors */ | ||||
|     private final HashMap<WebSocket, UserMetadata> newUsers; | ||||
|  | ||||
|     /** A set of users that can reconnect if they wish to do so, and their matching Participants */ | ||||
|     private final HashMap<WebSocket, Participant> readyToReconnect; | ||||
|  | ||||
|     /** A set of users that only have to send the | ||||
|      * {@link uulm.teamname.marvelous.gamelibrary.messages.client.PlayerReadyMessage} to be assigned | ||||
|      * to a lobby | ||||
|      */ | ||||
|     private final HashMap<WebSocket, UserMetadata> readyToConnect; | ||||
|  | ||||
|  | ||||
|     /** A set of users that are already assigned to lobbies or character selection */ | ||||
|     private final HashSet<WebSocket> inGame; | ||||
|  | ||||
|     /** | ||||
|      * A map mapping {@link SUID SUIDs} to {@link Participant Participants} to assert whether (and where to) reconnect | ||||
|      * an user | ||||
|      */ | ||||
|     private final HashMap<SUID, Participant> activeParticipants; | ||||
|  | ||||
|     private final JSON json; | ||||
|  | ||||
|     /** Constructs a new, empty UserManager */ | ||||
|     public UserManager(JSON json) { | ||||
|         this.newUsers = new HashMap<>(); | ||||
|         this.readyToConnect = new HashMap<>(); | ||||
|         this.readyToReconnect = new HashMap<>(); | ||||
|         this.inGame = new HashSet<>(); | ||||
|         this.activeParticipants = new HashMap<>(); | ||||
|         this.json = json; | ||||
|     } | ||||
|  | ||||
|     /** Called on a new WebSocket connection. Places the WebSocket and its ResourceDescriptor in a HashMap. */ | ||||
|     public void connectUser(WebSocket conn, ClientHandshake handshake) { | ||||
|         var metadata = new UserMetadata(conn, handshake.getResourceDescriptor()); | ||||
|         synchronized (newUsers) { | ||||
|             newUsers.put(conn, metadata); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public void messageReceived(WebSocket conn, String message) { | ||||
|         // TODO: Implement more than only a basic handshake | ||||
|  | ||||
|         var parsedMessageOptional = json.parse(message); | ||||
|         if (parsedMessageOptional.isEmpty()) { | ||||
|             Logger.debug("Invalid message '{}' received, sending error", message); | ||||
|             sendError(conn, "Message could not be parsed"); | ||||
|         } else { | ||||
|             var parsedMessage = parsedMessageOptional.get(); | ||||
|  | ||||
|             if (parsedMessage instanceof HelloServerMessage) | ||||
|                  handshake(conn, (HelloServerMessage) parsedMessage); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void handshake(WebSocket conn, HelloServerMessage message) { | ||||
|         if (!newUsers.containsKey(conn)) { | ||||
|             Logger.debug("websocket {} sent HelloServerMessage outside of handshake", conn); | ||||
|             sendError(conn, "Invalid message, as Handshake is already completed"); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         Logger.info("Performing handshake with user '{}'", message.name); | ||||
|  | ||||
|         SUID clientID = new SUID(message.name, message.deviceID); | ||||
|  | ||||
|         // check if client is reconnected | ||||
|         var participant = activeParticipants.get(clientID); | ||||
|         if (participant != null) { | ||||
|             synchronized (readyToReconnect) { | ||||
|                 readyToReconnect.put(conn, participant); | ||||
|             } | ||||
|         } else { | ||||
|             var metadata = newUsers.get(conn); | ||||
|             metadata.username = message.name; | ||||
|  | ||||
|             Logger.trace("removing handshaking user from newUsers"); | ||||
|             synchronized (newUsers) { | ||||
|                 newUsers.remove(conn); | ||||
|             } | ||||
|             Logger.trace("adding handshaking user to readyToConnect"); | ||||
|             synchronized (readyToConnect) { | ||||
|                 readyToConnect.put(conn, metadata); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** Sends an {@link uulm.teamname.marvelous.gamelibrary.messages.ErrorMessage} to the specified user. */ | ||||
|     public void sendError(WebSocket conn, String error) { | ||||
|         Logger.debug("Sending error message '{}' to WebSocket {}", error, conn); | ||||
|         var errorMessage = new ErrorMessage(); | ||||
|         errorMessage.message = error; | ||||
|         conn.send(json.stringify(errorMessage).get()); | ||||
|     } | ||||
|  | ||||
|     public void disconnectUser(WebSocket conn, boolean closedByRemote) { | ||||
|         // TODO: notify clients and such if the connection was in fact closed by the remote. Also remove participant | ||||
|         synchronized (newUsers) { | ||||
|             newUsers.remove(conn); | ||||
|         } | ||||
|         synchronized (inGame) { | ||||
|             inGame.remove(conn); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public boolean isUserConnected(WebSocket user) { | ||||
|         return newUsers.containsKey(user) || | ||||
|                 readyToReconnect.containsKey(user) || | ||||
|                 readyToConnect.containsKey(user) || | ||||
|                 inGame.contains(user); | ||||
|     } | ||||
|  | ||||
|     public int getUserCount() { | ||||
|         return newUsers.size() + readyToConnect.size() + readyToReconnect.size() + inGame.size(); | ||||
|     } | ||||
|  | ||||
|     public Map<WebSocket, UserMetadata> getNewUsers() { | ||||
|         return Collections.unmodifiableMap(newUsers); | ||||
|     } | ||||
|  | ||||
|     public Map<WebSocket, Participant> getReadyToReconnect() { | ||||
|         return Collections.unmodifiableMap(readyToReconnect); | ||||
|     } | ||||
|  | ||||
|     public Map<WebSocket, UserMetadata> getReadyToConnect() { | ||||
|         return Collections.unmodifiableMap(readyToConnect); | ||||
|     } | ||||
|  | ||||
|     public Set<WebSocket> getInGame() { | ||||
|         return Collections.unmodifiableSet(inGame); | ||||
|     } | ||||
|  | ||||
|     public Map<SUID, Participant> getActiveParticipants() { | ||||
|         return Collections.unmodifiableMap(activeParticipants); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,14 @@ | ||||
| package uulm.teamname.marvelous.server.netconnector; | ||||
|  | ||||
| import org.java_websocket.WebSocket; | ||||
|  | ||||
| class UserMetadata { | ||||
|     public String username; | ||||
|     public final WebSocket conn; | ||||
|     public final String resourceDescriptor; | ||||
|  | ||||
|     public UserMetadata(WebSocket conn, String resourceDescriptor) { | ||||
|         this.conn = conn; | ||||
|         this.resourceDescriptor = resourceDescriptor; | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user