diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/netconnector/MarvelousServer.java b/Server/src/main/java/uulm/teamname/marvelous/server/netconnector/MarvelousServer.java index b908f73..5cb33c0 100644 --- a/Server/src/main/java/uulm/teamname/marvelous/server/netconnector/MarvelousServer.java +++ b/Server/src/main/java/uulm/teamname/marvelous/server/netconnector/MarvelousServer.java @@ -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. NEVER USE ANYWHERE ELSE! + * @param userManager*/ + protected MarvelousServer(UserManager userManager) { super(); + this.userManager = userManager; } } diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/netconnector/SUID.java b/Server/src/main/java/uulm/teamname/marvelous/server/netconnector/SUID.java new file mode 100644 index 0000000..5e2d29d --- /dev/null +++ b/Server/src/main/java/uulm/teamname/marvelous/server/netconnector/SUID.java @@ -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 + '\'' + + '}'; + } +} diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/netconnector/UserManager.java b/Server/src/main/java/uulm/teamname/marvelous/server/netconnector/UserManager.java new file mode 100644 index 0000000..11b3eef --- /dev/null +++ b/Server/src/main/java/uulm/teamname/marvelous/server/netconnector/UserManager.java @@ -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 newUsers; + + /** A set of users that can reconnect if they wish to do so, and their matching Participants */ + private final HashMap 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 readyToConnect; + + + /** A set of users that are already assigned to lobbies or character selection */ + private final HashSet inGame; + + /** + * A map mapping {@link SUID SUIDs} to {@link Participant Participants} to assert whether (and where to) reconnect + * an user + */ + private final HashMap 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 getNewUsers() { + return Collections.unmodifiableMap(newUsers); + } + + public Map getReadyToReconnect() { + return Collections.unmodifiableMap(readyToReconnect); + } + + public Map getReadyToConnect() { + return Collections.unmodifiableMap(readyToConnect); + } + + public Set getInGame() { + return Collections.unmodifiableSet(inGame); + } + + public Map getActiveParticipants() { + return Collections.unmodifiableMap(activeParticipants); + } +} diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/netconnector/UserMetadata.java b/Server/src/main/java/uulm/teamname/marvelous/server/netconnector/UserMetadata.java new file mode 100644 index 0000000..535a238 --- /dev/null +++ b/Server/src/main/java/uulm/teamname/marvelous/server/netconnector/UserMetadata.java @@ -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; + } +}