getInputClass() { return LoginCredentials.class; }
+}
diff --git a/src/main/java/envoy/server/processors/MessageProcessor.java b/src/main/java/envoy/server/processors/MessageProcessor.java
new file mode 100755
index 0000000..e4058e2
--- /dev/null
+++ b/src/main/java/envoy/server/processors/MessageProcessor.java
@@ -0,0 +1,64 @@
+package envoy.server.processors;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.persistence.EntityExistsException;
+
+import envoy.data.Message;
+import envoy.event.MessageStatusChange;
+import envoy.server.data.PersistenceManager;
+import envoy.server.net.ConnectionManager;
+import envoy.server.net.ObjectWriteProxy;
+import envoy.util.EnvoyLog;
+
+/**
+ * This {@link ObjectProcessor} handles incoming {@link Message}s.
+ *
+ * Project: envoy-server-standalone
+ * File: MessageProcessor.java
+ * Created: 30.12.2019
+ *
+ * @author Kai S. K. Engelbart
+ * @author Maximilian Käfer
+ * @since Envoy Server Standalone v0.1-alpha
+ */
+public class MessageProcessor implements ObjectProcessor {
+
+ private static final PersistenceManager persistenceManager = PersistenceManager.getInstance();
+ private static final ConnectionManager connectionManager = ConnectionManager.getInstance();
+ private static final Logger logger = EnvoyLog.getLogger(MessageProcessor.class);
+
+ @Override
+ public void process(Message message, long socketID, ObjectWriteProxy writeProxy) {
+ message.nextStatus();
+
+ // Convert to server message
+ final var serverMessage = new envoy.server.data.Message(message);
+
+ try {
+
+ // Persist the message
+ persistenceManager.addMessage(serverMessage);
+
+ // Send the message to the recipient if online
+ if (connectionManager.isOnline(message.getRecipientID())) {
+ writeProxy.write(connectionManager.getSocketID(message.getRecipientID()), message);
+
+ // Increment status
+ message.nextStatus();
+ serverMessage.received();
+ persistenceManager.updateMessage(serverMessage);
+
+ // Notify the sender about the delivery
+ // Note that the exact time stamp might differ slightly
+ writeProxy.write(socketID, new MessageStatusChange(message));
+ }
+ } catch (EntityExistsException e) {
+ logger.log(Level.WARNING, "Received " + message + " with an ID that already exists!");
+ }
+ }
+
+ @Override
+ public Class getInputClass() { return Message.class; }
+}
diff --git a/src/main/java/envoy/server/processors/MessageStatusChangeProcessor.java b/src/main/java/envoy/server/processors/MessageStatusChangeProcessor.java
new file mode 100755
index 0000000..1c76f77
--- /dev/null
+++ b/src/main/java/envoy/server/processors/MessageStatusChangeProcessor.java
@@ -0,0 +1,48 @@
+package envoy.server.processors;
+
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import envoy.data.Message.MessageStatus;
+import envoy.event.MessageStatusChange;
+import envoy.server.data.PersistenceManager;
+import envoy.server.net.ConnectionManager;
+import envoy.server.net.ObjectWriteProxy;
+import envoy.util.EnvoyLog;
+
+/**
+ * Project: envoy-server-standalone
+ * File: MessageStatusChangeProcessor.java
+ * Created: 10 Jan 2020
+ *
+ * @author Leon Hofmeister
+ * @since Envoy Server Standalone v0.1-alpha
+ */
+public class MessageStatusChangeProcessor implements ObjectProcessor {
+
+ private final ConnectionManager connectionManager = ConnectionManager.getInstance();
+ private final PersistenceManager persistenceManager = PersistenceManager.getInstance();
+ private final Logger logger = EnvoyLog.getLogger(MessageStatusChangeProcessor.class);
+
+ @Override
+ public void process(MessageStatusChange statusChange, long socketID, ObjectWriteProxy writeProxy) throws IOException {
+
+ // Any other status than READ is not supposed to be sent to the server
+ if (statusChange.get() != MessageStatus.READ) {
+ logger.log(Level.WARNING, "Invalid " + statusChange);
+ return;
+ }
+
+ final var msg = persistenceManager.getMessageByID(statusChange.getID());
+ msg.read();
+ persistenceManager.updateMessage(msg);
+
+ // Notifies the sender of the message about the status-update to READ
+ final long senderID = msg.getSender().getID();
+ if (connectionManager.isOnline(senderID)) writeProxy.write(connectionManager.getSocketID(senderID), statusChange);
+ }
+
+ @Override
+ public Class getInputClass() { return MessageStatusChange.class; }
+}
diff --git a/src/main/java/envoy/server/processors/NameChangeProcessor.java b/src/main/java/envoy/server/processors/NameChangeProcessor.java
new file mode 100644
index 0000000..75f713c
--- /dev/null
+++ b/src/main/java/envoy/server/processors/NameChangeProcessor.java
@@ -0,0 +1,34 @@
+package envoy.server.processors;
+
+import java.io.IOException;
+
+import envoy.event.NameChange;
+import envoy.server.data.Contact;
+import envoy.server.data.PersistenceManager;
+import envoy.server.net.ObjectWriteProxy;
+
+/**
+ * Project: envoy-server-standalone
+ * File: NameChangeProcessor.java
+ * Created: 26 Mar 2020
+ *
+ * @author Leon Hofmeister
+ * @since Envoy Server Standalone v0.1-beta
+ */
+public class NameChangeProcessor implements ObjectProcessor {
+
+ private static final PersistenceManager persistenceManager = PersistenceManager.getInstance();
+
+ @Override
+ public void process(NameChange nameChange, long socketID, ObjectWriteProxy writeProxy) throws IOException {
+ Contact toUpdate = persistenceManager.getContactByID(nameChange.getID());
+ toUpdate.setName(nameChange.get());
+ persistenceManager.updateContact(toUpdate);
+
+ // Notify online contacts of the name change
+ writeProxy.writeToOnlineContacts(toUpdate.getContacts(), nameChange);
+ }
+
+ @Override
+ public Class getInputClass() { return NameChange.class; }
+}
diff --git a/src/main/java/envoy/server/processors/ObjectProcessor.java b/src/main/java/envoy/server/processors/ObjectProcessor.java
new file mode 100755
index 0000000..a542d8f
--- /dev/null
+++ b/src/main/java/envoy/server/processors/ObjectProcessor.java
@@ -0,0 +1,35 @@
+package envoy.server.processors;
+
+import java.io.IOException;
+
+import envoy.server.net.ObjectWriteProxy;
+
+/**
+ * This interface defines methods for processing objects of a specific
+ * type incoming from a client.
+ *
+ * Project: envoy-server-standalone
+ * File: ObjectProcessor.java
+ * Created: 30.12.2019
+ *
+ * @author Kai S. K. Engelbart
+ * @param type of the request object
+ * @since Envoy Server Standalone v0.1-alpha
+ */
+public interface ObjectProcessor {
+
+ /**
+ * @return the class of the request object
+ * @since Envoy Server Standalone v0.1-alpha
+ */
+ Class getInputClass();
+
+ /**
+ * @param input the request object
+ * @param socketID the ID of the socket from which the object was received
+ * @param writeProxy the object that allows writing to a client
+ * @throws IOException if something went wrong during processing
+ * @since Envoy Server Standalone v0.1-alpha
+ */
+ void process(T input, long socketID, ObjectWriteProxy writeProxy) throws IOException;
+}
diff --git a/src/main/java/envoy/server/processors/UserStatusChangeProcessor.java b/src/main/java/envoy/server/processors/UserStatusChangeProcessor.java
new file mode 100755
index 0000000..87dd179
--- /dev/null
+++ b/src/main/java/envoy/server/processors/UserStatusChangeProcessor.java
@@ -0,0 +1,77 @@
+package envoy.server.processors;
+
+import java.util.logging.Logger;
+
+import envoy.data.User.UserStatus;
+import envoy.event.UserStatusChange;
+import envoy.server.data.PersistenceManager;
+import envoy.server.data.User;
+import envoy.server.net.ObjectWriteProxy;
+import envoy.util.EnvoyLog;
+
+/**
+ * This processor handles incoming {@link UserStatusChange}.
+ *
+ * Project: envoy-server-standalone
+ * File: UserStatusChangeProcessor.java
+ * Created: 1 Feb 2020
+ *
+ * @author Leon Hofmeister
+ * @since Envoy Server Standalone v0.1-alpha
+ */
+public class UserStatusChangeProcessor implements ObjectProcessor {
+
+ private static ObjectWriteProxy writeProxy;
+
+ private static final PersistenceManager persistenceManager = PersistenceManager.getInstance();
+ private static final Logger logger = EnvoyLog.getLogger(UserStatusChangeProcessor.class);
+
+ @Override
+ public Class getInputClass() { return UserStatusChange.class; }
+
+ @Override
+ public void process(UserStatusChange input, long socketID, ObjectWriteProxy writeProxy) {
+ // new status should not equal old status
+ if (input.get().equals(persistenceManager.getUserByID(input.getID()).getStatus())) {
+ logger.warning("Received an unnecessary UserStatusChange");
+ return;
+ }
+ updateUserStatus(input);
+ }
+
+ /**
+ * Sets the {@link UserStatus} for a given user. Both offline contacts and
+ * currently online contacts are notified.
+ *
+ * @param user the {@link UserStatusChange} that signals the change
+ * @since Envoy Server Standalone v0.1-alpha
+ */
+
+ public static void updateUserStatus(User user) {
+
+ // Handling for newly logged in clients
+ persistenceManager.updateContact(user);
+
+ // Handling for contacts that are already online
+ writeProxy.writeToOnlineContacts(user.getContacts(), new UserStatusChange(user.getID(), user.getStatus()));
+ }
+
+ /**
+ * @param evt the {@link UserStatusChange}
+ * @since Envoy Server Standalone v0.1-alpha
+ */
+ public static void updateUserStatus(UserStatusChange evt) { updateUserStatus(persistenceManager.getUserByID(evt.getID())); }
+
+ /**
+ * This method is only called by the LoginCredentialProcessor because every
+ * user needs to login (open a socket) before changing his status.
+ * Needed to ensure propagation of events because an uninitialized writeProxy
+ * would cause problems.
+ *
+ * @param writeProxy the writeProxy that is used to send objects back to clients
+ * @since Envoy Server Standalone v0.1-alpha
+ */
+ public static void setWriteProxy(ObjectWriteProxy writeProxy) { UserStatusChangeProcessor.writeProxy = writeProxy; }
+ // TODO may cause an problem if two clients log in at the same time.
+ // Change Needed.
+}
diff --git a/src/main/java/envoy/server/processors/package-info.java b/src/main/java/envoy/server/processors/package-info.java
new file mode 100755
index 0000000..d96d161
--- /dev/null
+++ b/src/main/java/envoy/server/processors/package-info.java
@@ -0,0 +1,10 @@
+/**
+ * This package contains all classes that process data received from client
+ * connections.
+ *
+ * @author Kai S. K. Engelbart
+ * @author Leon Hofmeister
+ * @author Maximilian Käfer
+ * @since Envoy v0.1-alpha
+ */
+package envoy.server.processors;
diff --git a/src/main/java/envoy/server/util/PasswordUtil.java b/src/main/java/envoy/server/util/PasswordUtil.java
new file mode 100644
index 0000000..5d0ba7a
--- /dev/null
+++ b/src/main/java/envoy/server/util/PasswordUtil.java
@@ -0,0 +1,98 @@
+package envoy.server.util;
+
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+
+/**
+ * Provides a password hashing and comparison mechanism using the
+ * {@code PBKDF2WithHmacSHA1} algorithm.
+ *
+ * Project: envoy-server-standalone
+ * File: PasswordUtil.java
+ * Created: 10.07.2020
+ *
+ * @author Kai S. K. Engelbart
+ * @since Envoy Server Standalone v0.1-beta
+ */
+public class PasswordUtil {
+
+ private static final int ITERATIONS = 1000;
+ private static final int KEY_LENGTH = 64 * 8;
+ private static final String SALT_ALGORITHM = "SHA1PRNG";
+ private static final String SECRET_KEY_ALGORITHM = "PBKDF2WithHmacSHA1";
+
+ private PasswordUtil() {}
+
+ /**
+ * Validates a password against a stored password hash
+ *
+ * @param current the password to validate
+ * @param stored the hash to validate against
+ * @return {@code true} if the password is correct
+ * @since Envoy Server Standalone v0.1-beta
+ */
+ public static boolean validate(String current, String stored) {
+ try {
+ String[] parts = stored.split(":");
+ int iterations = Integer.parseInt(parts[0]);
+ byte[] salt = fromHex(parts[1]);
+ byte[] hash = fromHex(parts[2]);
+
+ var spec = new PBEKeySpec(current.toCharArray(), salt, iterations, KEY_LENGTH);
+ var skf = SecretKeyFactory.getInstance(SECRET_KEY_ALGORITHM);
+ byte[] testHash = skf.generateSecret(spec).getEncoded();
+
+ int diff = hash.length ^ testHash.length;
+ for (int i = 0; i < hash.length && i < testHash.length; ++i)
+ diff |= hash[i] ^ testHash[i];
+
+ return diff == 0;
+ } catch (GeneralSecurityException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Creates a parameterized salted password hash.
+ *
+ * @param password the password to hash
+ * @return a result string in the form of {@code iterations:salt:hash}
+ * @since Envoy Server Standalone v0.1-beta
+ */
+ public static String hash(String password) {
+ try {
+ byte[] salt = salt();
+ var spec = new PBEKeySpec(password.toCharArray(), salt, ITERATIONS, KEY_LENGTH);
+ var skf = SecretKeyFactory.getInstance(SECRET_KEY_ALGORITHM);
+ byte[] hash = skf.generateSecret(spec).getEncoded();
+ return ITERATIONS + ":" + toHex(salt) + ":" + toHex(hash);
+ } catch (GeneralSecurityException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static byte[] salt() throws NoSuchAlgorithmException {
+ SecureRandom sr = SecureRandom.getInstance(SALT_ALGORITHM);
+ byte[] salt = new byte[16];
+ sr.nextBytes(salt);
+ return salt;
+ }
+
+ private static String toHex(byte[] bytes) {
+ String hex = new BigInteger(1, bytes).toString(16);
+ int padding = bytes.length * 2 - hex.length();
+ return padding > 0 ? String.format("%0" + padding + "d", 0) + hex : hex;
+ }
+
+ private static byte[] fromHex(String hex) {
+ byte[] bytes = new byte[hex.length() / 2];
+ for (int i = 0; i < bytes.length; ++i)
+ bytes[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16);
+ return bytes;
+ }
+}
diff --git a/src/main/java/envoy/server/util/VersionUtil.java b/src/main/java/envoy/server/util/VersionUtil.java
new file mode 100644
index 0000000..8f623e1
--- /dev/null
+++ b/src/main/java/envoy/server/util/VersionUtil.java
@@ -0,0 +1,94 @@
+package envoy.server.util;
+
+import java.util.regex.Pattern;
+
+/**
+ * Implements a comparison algorithm between Envoy versions and defines minimal
+ * and maximal client versions compatible with this server.
+ *
+ * Project: envoy-server-standalone
+ * File: VersionUtil.java
+ * Created: 23.06.2020
+ *
+ * @author Kai S. K. Engelbart
+ * @since Envoy Server Standalone v0.1-beta
+ */
+public class VersionUtil {
+
+ /**
+ * The minimal client version compatible with this server.
+ *
+ * @since Envoy Server Standalone v0.1-beta
+ */
+ public static final String MIN_CLIENT_VERSION = "0.1-beta";
+
+ /**
+ * The maximal client version compatible with this server.
+ *
+ * @since Envoy Server Standalone v0.1-beta
+ */
+ public static final String MAX_CLIENT_VERSION = "0.1-beta";
+
+ private static final Pattern versionPattern = Pattern.compile("(?\\d).(?\\d)(?:-(?\\w+))?");
+
+ private VersionUtil() {}
+
+ /**
+ * Parses an Envoy Client version string and checks whether that version is
+ * compatible with this server.
+ *
+ * @param version the version string to parse
+ * @return {@code true} if the given version is compatible with this server
+ * @since Envoy Server Standalone v0.1-beta
+ */
+ public static boolean verifyCompatibility(String version) {
+ final var currentMatcher = versionPattern.matcher(version);
+
+ if (!currentMatcher.matches()) return false;
+
+ final var minMatcher = versionPattern.matcher(MIN_CLIENT_VERSION);
+ final var maxMatcher = versionPattern.matcher(MAX_CLIENT_VERSION);
+
+ if (!minMatcher.matches() || !maxMatcher.matches()) throw new RuntimeException("Invalid min or max client version configured!");
+
+ // Compare suffixes
+ {
+ final var currentSuffix = convertSuffix(currentMatcher.group("suffix"));
+ final var minSuffix = convertSuffix(minMatcher.group("suffix"));
+ final var maxSuffix = convertSuffix(maxMatcher.group("suffix"));
+
+ if (currentSuffix < minSuffix || currentSuffix > maxSuffix) return false;
+ }
+
+ // Compare major
+ {
+ final var currentMajor = Integer.parseInt(currentMatcher.group("major"));
+ final var minMajor = Integer.parseInt(minMatcher.group("major"));
+ final var maxMajor = Integer.parseInt(maxMatcher.group("major"));
+
+ if (currentMajor < minMajor || currentMajor > maxMajor) return false;
+ }
+
+ // Compare minor
+ {
+ final var currentMinor = Integer.parseInt(currentMatcher.group("minor"));
+ final var minMinor = Integer.parseInt(minMatcher.group("minor"));
+ final var maxMinor = Integer.parseInt(maxMatcher.group("minor"));
+
+ if (currentMinor < minMinor || currentMinor > maxMinor) return false;
+ }
+
+ return true;
+ }
+
+ private static int convertSuffix(String suffix) {
+ switch (suffix == null ? "" : suffix) {
+ case "alpha":
+ return 0;
+ case "beta":
+ return 1;
+ default:
+ return 2;
+ }
+ }
+}
diff --git a/src/main/java/envoy/server/util/package-info.java b/src/main/java/envoy/server/util/package-info.java
new file mode 100644
index 0000000..f052882
--- /dev/null
+++ b/src/main/java/envoy/server/util/package-info.java
@@ -0,0 +1,11 @@
+/**
+ * This package contains utility classes used in Envoy Server.
+ *
+ * Project: envoy-server-standalone
+ * File: package-info.java
+ * Created: 23.06.2020
+ *
+ * @author Kai S. K. Engelbart
+ * @since Envoy Server Standalone v0.1-beta
+ */
+package envoy.server.util;
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
new file mode 100755
index 0000000..860f1df
--- /dev/null
+++ b/src/main/java/module-info.java
@@ -0,0 +1,20 @@
+/**
+ * This module contains all classes defining the server application of the Envoy
+ * project.
+ *
+ * @author Kai S. K. Engelbart
+ * @author Leon Hofmeister
+ * @author Maximilian Käfer
+ * @since Envoy Server Standalone v0.1-beta
+ */
+module envoy.server {
+
+ opens envoy.server.data;
+
+ requires transitive envoy.common;
+ requires transitive java.nio.server;
+ requires transitive java.persistence;
+ requires transitive java.sql;
+ requires transitive org.hibernate.orm.core;
+
+}
diff --git a/src/main/resources/META-INF/persistence.xml b/src/main/resources/META-INF/persistence.xml
new file mode 100755
index 0000000..8e0af7e
--- /dev/null
+++ b/src/main/resources/META-INF/persistence.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+