Did some cleanup, removed deprecated and unused classes, added javadoc.

This commit is contained in:
Andrew Lalis 2021-09-24 11:09:29 +02:00
parent 80f4cf2035
commit cd7d40cd7d
18 changed files with 166 additions and 228 deletions

View File

@ -1,16 +0,0 @@
package nl.andrewl.concord_client.event.handlers;
import nl.andrewl.concord_client.ConcordClient;
import nl.andrewl.concord_client.event.MessageHandler;
import nl.andrewl.concord_core.msg.types.ChannelUsersResponse;
/**
* When the client receives information about the list of known users, it will
* update its model to show the new list.
*/
public class ChannelUsersResponseHandler implements MessageHandler<ChannelUsersResponse> {
@Override
public void handle(ChannelUsersResponse msg, ConcordClient client) throws Exception {
client.getModel().setKnownUsers(msg.getUsers());
}
}

View File

@ -23,6 +23,26 @@ import java.util.List;
* Utility class for handling the establishment of encrypted communication. * Utility class for handling the establishment of encrypted communication.
*/ */
public class Encryption { public class Encryption {
/**
* Upgrades the given input and output streams to a pair of cipher input and
* output streams. This upgrade follows the following steps:
* <ol>
* <li>Generate an elliptic curve key pair, and send the public key to the output stream.</li>
* <li>Read the public key that the other person has sent, from the input stream.</li>
* <li>Compute a shared private key using the ECDH key exchange, with our private key and their public key.</li>
* <li>Create the cipher streams from the shared private key.</li>
* </ol>
* @param in The unencrypted input stream.
* @param out The unencrypted output stream.
* @param serializer The message serializer that is used to read and write
* messages according to the standard Concord protocol.
* @return The pair of cipher streams, which can be used to send encrypted
* messages.
* @throws GeneralSecurityException If an error occurs while generating keys
* or preparing the cipher streams.
* @throws IOException If an error occurs while reading or writing data on
* the streams.
*/
public static Pair<CipherInputStream, CipherOutputStream> upgrade( public static Pair<CipherInputStream, CipherOutputStream> upgrade(
InputStream in, InputStream in,
OutputStream out, OutputStream out,

View File

@ -36,7 +36,7 @@ public class Serializer {
registerType(3, MoveToChannel.class); registerType(3, MoveToChannel.class);
registerType(4, ChatHistoryRequest.class); registerType(4, ChatHistoryRequest.class);
registerType(5, ChatHistoryResponse.class); registerType(5, ChatHistoryResponse.class);
registerType(6, ChannelUsersRequest.class); // Type id 6 removed due to deprecation.
registerType(7, ServerUsers.class); registerType(7, ServerUsers.class);
registerType(8, ServerMetaData.class); registerType(8, ServerMetaData.class);
registerType(9, Error.class); registerType(9, Error.class);

View File

@ -1,36 +0,0 @@
package nl.andrewl.concord_core.msg.types;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import nl.andrewl.concord_core.msg.Message;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.UUID;
import static nl.andrewl.concord_core.msg.MessageUtils.*;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Deprecated
public class ChannelUsersRequest implements Message {
private UUID channelId;
@Override
public int getByteCount() {
return UUID_BYTES;
}
@Override
public void write(DataOutputStream o) throws IOException {
writeUUID(this.channelId, o);
}
@Override
public void read(DataInputStream i) throws IOException {
this.channelId = readUUID(i);
}
}

View File

@ -1,43 +0,0 @@
package nl.andrewl.concord_core.msg.types;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import nl.andrewl.concord_core.msg.Message;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.List;
import static nl.andrewl.concord_core.msg.MessageUtils.*;
/**
* This message is sent from the server to the client when the information about
* the users in the channel that a client is in has changed. For example, when
* a user leaves a channel, all others in that channel will be sent this message
* to indicate that update.
* @deprecated Clients will be updated via a {@link ServerUsers} message.
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Deprecated
public class ChannelUsersResponse implements Message {
private List<UserData> users;
@Override
public int getByteCount() {
return getByteSize(this.users);
}
@Override
public void write(DataOutputStream o) throws IOException {
writeList(this.users, o);
}
@Override
public void read(DataInputStream i) throws IOException {
this.users = readList(UserData.class, i);
}
}

View File

@ -77,6 +77,10 @@ public class ChatHistoryRequest implements Message {
.collect(Collectors.joining(";")); .collect(Collectors.joining(";"));
} }
/**
* Utility method to extract the query string's values as a key-value map.
* @return A map of the query parameters.
*/
public Map<String, String> getQueryAsMap() { public Map<String, String> getQueryAsMap() {
String[] pairs = this.query.split(";"); String[] pairs = this.query.split(";");
if (pairs.length == 0) return Map.of(); if (pairs.length == 0) return Map.of();

View File

@ -145,7 +145,7 @@ public class ConcordServer implements Runnable {
private void shutdown() { private void shutdown() {
System.out.println("Shutting down the server."); System.out.println("Shutting down the server.");
for (var clientId : this.clientManager.getConnectedIds()) { for (var clientId : this.clientManager.getConnectedIds()) {
this.clientManager.deregisterClient(clientId); this.clientManager.handleLogOut(clientId);
} }
this.scheduledExecutorService.shutdown(); this.scheduledExecutorService.shutdown();
this.executorService.shutdown(); this.executorService.shutdown();
@ -161,13 +161,7 @@ public class ConcordServer implements Runnable {
public void run() { public void run() {
this.running = true; this.running = true;
this.scheduledExecutorService.scheduleAtFixedRate(this.discoveryServerPublisher::publish, 0, 1, TimeUnit.MINUTES); this.scheduledExecutorService.scheduleAtFixedRate(this.discoveryServerPublisher::publish, 0, 1, TimeUnit.MINUTES);
StringBuilder startupMessage = new StringBuilder(); System.out.printf("Opened server on port %d.\n", config.getPort());
startupMessage.append("Opened server on port ").append(config.getPort()).append("\n")
.append("The following channels are available:\n");
for (var channel : this.channelManager.getChannels()) {
startupMessage.append("\tChannel \"").append(channel).append('\n');
}
System.out.println(startupMessage);
while (this.running) { while (this.running) {
try { try {
Socket socket = this.serverSocket.accept(); Socket socket = this.serverSocket.accept();

View File

@ -21,11 +21,14 @@ import java.util.concurrent.ConcurrentHashMap;
* clients in a server. * clients in a server.
*/ */
@Getter @Getter
public class Channel { public class Channel implements Comparable<Channel> {
private final ConcordServer server; private final ConcordServer server;
private final UUID id; private final UUID id;
private String name; private String name;
/**
* The set of clients that are connected to this channel.
*/
private final Set<ClientThread> connectedClients; private final Set<ClientThread> connectedClients;
/** /**
@ -94,6 +97,10 @@ public class Channel {
return users; return users;
} }
public String getAsTag() {
return "#" + this.name;
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
@ -111,4 +118,9 @@ public class Channel {
public String toString() { public String toString() {
return this.name + " (" + this.id + ")"; return this.name + " (" + this.id + ")";
} }
@Override
public int compareTo(Channel o) {
return this.getName().compareTo(o.getName());
}
} }

View File

@ -1,9 +1,8 @@
package nl.andrewl.concord_server.cli; package nl.andrewl.concord_server.cli;
import nl.andrewl.concord_server.ConcordServer; import nl.andrewl.concord_server.ConcordServer;
import nl.andrewl.concord_server.cli.command.AddChannelCommand; import nl.andrewl.concord_server.cli.command.ChannelCommand;
import nl.andrewl.concord_server.cli.command.ListClientsCommand; import nl.andrewl.concord_server.cli.command.ListClientsCommand;
import nl.andrewl.concord_server.cli.command.RemoveChannelCommand;
import nl.andrewl.concord_server.cli.command.StopCommand; import nl.andrewl.concord_server.cli.command.StopCommand;
import java.io.BufferedReader; import java.io.BufferedReader;
@ -12,6 +11,10 @@ import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
/**
* Simple command-line interface that's available when running the server. This
* accepts commands and various space-separated arguments.
*/
public class ServerCli implements Runnable { public class ServerCli implements Runnable {
private final ConcordServer server; private final ConcordServer server;
private final Map<String, ServerCliCommand> commands; private final Map<String, ServerCliCommand> commands;
@ -21,8 +24,7 @@ public class ServerCli implements Runnable {
this.commands = new HashMap<>(); this.commands = new HashMap<>();
this.commands.put("list-clients", new ListClientsCommand()); this.commands.put("list-clients", new ListClientsCommand());
this.commands.put("add-channel", new AddChannelCommand()); this.commands.put("channel", new ChannelCommand());
this.commands.put("remove-channel", new RemoveChannelCommand());
this.commands.put("stop", new StopCommand()); this.commands.put("stop", new StopCommand());
this.commands.put("help", (s, args) -> { this.commands.put("help", (s, args) -> {
@ -35,7 +37,7 @@ public class ServerCli implements Runnable {
@Override @Override
public void run() { public void run() {
System.out.println("Server command-line-interface initialized. Type \"help\" for a list of available commands."); System.out.println("Server command-line-interface initialized.\n\tType \"help\" for a list of available commands.\n\tType \"stop\" to stop the server.");
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String line; String line;
try { try {

View File

@ -1,41 +0,0 @@
package nl.andrewl.concord_server.cli.command;
import nl.andrewl.concord_server.ConcordServer;
import nl.andrewl.concord_server.channel.Channel;
import nl.andrewl.concord_server.cli.ServerCliCommand;
import nl.andrewl.concord_server.config.ServerConfig;
import java.util.UUID;
/**
* This command adds a new channel to the server.
*/
public class AddChannelCommand implements ServerCliCommand {
@Override
public void handle(ConcordServer server, String[] args) throws Exception {
if (args.length < 1) {
System.err.println("Missing required name argument.");
return;
}
String name = args[0].trim().toLowerCase().replaceAll("\\s+", "-");
if (name.isBlank()) {
System.err.println("Cannot create channel with blank name.");
return;
}
if (server.getChannelManager().getChannelByName(name).isPresent()) {
System.err.println("Channel with that name already exists.");
return;
}
String description = null;
if (args.length > 1) {
description = args[1].trim();
}
UUID id = server.getIdProvider().newId();
var channelConfig = new ServerConfig.ChannelConfig(id.toString(), name, description);
server.getConfig().getChannels().add(channelConfig);
server.getConfig().save();
server.getChannelManager().addChannel(new Channel(server, id, name));
server.getClientManager().broadcast(server.getMetaData());
}
}

View File

@ -0,0 +1,105 @@
package nl.andrewl.concord_server.cli.command;
import nl.andrewl.concord_server.ConcordServer;
import nl.andrewl.concord_server.channel.Channel;
import nl.andrewl.concord_server.cli.ServerCliCommand;
import nl.andrewl.concord_server.config.ServerConfig;
import java.io.IOException;
import java.util.Arrays;
import java.util.Optional;
import java.util.UUID;
/**
* Command for interacting with channels on the server.
*/
public class ChannelCommand implements ServerCliCommand {
@Override
public void handle(ConcordServer server, String[] args) throws Exception {
if (args.length < 1) {
System.err.println("Missing required subcommand. Valid subcommands are: add, remove, list");
return;
}
String subcommand = args[0];
args = Arrays.copyOfRange(args, 1, args.length);
switch (subcommand) {
case "add" -> addChannel(server, args);
case "remove" -> removeChannel(server, args);
case "list" -> listChannels(server);
default -> System.err.println("Unknown subcommand.");
}
}
private void addChannel(ConcordServer server, String[] args) throws IOException {
if (args.length < 1) {
System.err.println("Missing required name argument.");
return;
}
String name = args[0].trim().toLowerCase().replaceAll("\\s+", "-");
if (name.isBlank()) {
System.err.println("Cannot create channel with blank name.");
return;
}
if (server.getChannelManager().getChannelByName(name).isPresent()) {
System.err.println("Channel with that name already exists.");
return;
}
String description = null;
if (args.length > 1) {
description = args[1].trim();
}
UUID id = server.getIdProvider().newId();
var channelConfig = new ServerConfig.ChannelConfig(id.toString(), name, description);
server.getConfig().getChannels().add(channelConfig);
server.getConfig().save();
var channel = new Channel(server, id, name);
server.getChannelManager().addChannel(channel);
server.getClientManager().broadcast(server.getMetaData());
System.out.println("Added channel " + channel.getAsTag() + ".");
}
private void removeChannel(ConcordServer server, String[] args) throws IOException {
if (args.length != 1) {
System.err.println("Missing required channel name.");
return;
}
String name = args[0].trim().toLowerCase();
Optional<Channel> optionalChannel = server.getChannelManager().getChannelByName(name);
if (optionalChannel.isEmpty()) {
System.err.println("No channel with that name exists.");
return;
}
Channel channelToRemove = optionalChannel.get();
Channel alternative = null;
for (var c : server.getChannelManager().getChannels()) {
if (!c.equals(channelToRemove)) {
alternative = c;
break;
}
}
if (alternative == null) {
System.err.println("No alternative channel could be found. A server must always have at least one channel.");
return;
}
for (var client : channelToRemove.getConnectedClients()) {
server.getChannelManager().moveToChannel(client, alternative);
}
server.getChannelManager().removeChannel(channelToRemove);
server.getDb().getContext().dropCollection(channelToRemove.getMessageCollection().getName());
server.getConfig().getChannels().removeIf(channelConfig -> channelConfig.getName().equals(channelToRemove.getName()));
server.getConfig().save();
server.getClientManager().broadcast(server.getMetaData());
System.out.println("Removed the channel " + channelToRemove);
}
private void listChannels(ConcordServer server) {
StringBuilder sb = new StringBuilder();
server.getChannelManager().getChannels().stream().sorted()
.forEachOrdered(channel -> sb.append(channel.getAsTag())
.append(" - ").append(channel.getConnectedClients().size())
.append(" users").append("\n")
);
System.out.print(sb);
}
}

View File

@ -1,44 +0,0 @@
package nl.andrewl.concord_server.cli.command;
import nl.andrewl.concord_server.channel.Channel;
import nl.andrewl.concord_server.ConcordServer;
import nl.andrewl.concord_server.cli.ServerCliCommand;
import java.util.Optional;
public class RemoveChannelCommand implements ServerCliCommand {
@Override
public void handle(ConcordServer server, String[] args) throws Exception {
if (args.length != 1) {
System.err.println("Missing required channel name.");
return;
}
String name = args[0].trim().toLowerCase();
Optional<Channel> optionalChannel = server.getChannelManager().getChannelByName(name);
if (optionalChannel.isEmpty()) {
System.err.println("No channel with that name exists.");
return;
}
Channel channelToRemove = optionalChannel.get();
Channel alternative = null;
for (var c : server.getChannelManager().getChannels()) {
if (!c.equals(channelToRemove)) {
alternative = c;
break;
}
}
if (alternative == null) {
System.err.println("No alternative channel could be found. A server must always have at least one channel.");
return;
}
for (var client : channelToRemove.getConnectedClients()) {
server.getChannelManager().moveToChannel(client, alternative);
}
server.getChannelManager().removeChannel(channelToRemove);
server.getDb().getContext().dropCollection(channelToRemove.getMessageCollection().getName());
server.getConfig().getChannels().removeIf(channelConfig -> channelConfig.getName().equals(channelToRemove.getName()));
server.getConfig().save();
server.getClientManager().broadcast(server.getMetaData());
System.out.println("Removed the channel " + channelToRemove);
}
}

View File

@ -43,10 +43,15 @@ public class ClientManager {
* the client. The server will register the client in its global set of * the client. The server will register the client in its global set of
* connected clients, and it will immediately move the client to the default * connected clients, and it will immediately move the client to the default
* channel. * channel.
* <p>
* If the client provides a session token with their identification
* message, then we should load their data from our database, otherwise
* we assume this is a new client.
* </p>
* @param identification The client's identification data. * @param identification The client's identification data.
* @param clientThread The client manager thread. * @param clientThread The client manager thread.
*/ */
public void registerClient(Identification identification, ClientThread clientThread) { public void handleLogIn(Identification identification, ClientThread clientThread) {
ClientConnectionData data; ClientConnectionData data;
try { try {
data = identification.getSessionToken() == null ? getNewClientData(identification) : getClientDataFromDb(identification); data = identification.getSessionToken() == null ? getNewClientData(identification) : getClientDataFromDb(identification);
@ -78,7 +83,7 @@ public class ClientManager {
* they're currently in. * they're currently in.
* @param clientId The id of the client to remove. * @param clientId The id of the client to remove.
*/ */
public void deregisterClient(UUID clientId) { public void handleLogOut(UUID clientId) {
var client = this.clients.remove(clientId); var client = this.clients.remove(clientId);
if (client != null) { if (client != null) {
client.getCurrentChannel().removeClient(client); client.getCurrentChannel().removeClient(client);

View File

@ -112,7 +112,7 @@ public class ClientThread extends Thread {
} }
if (this.clientId != null) { if (this.clientId != null) {
this.server.getClientManager().deregisterClient(this.clientId); this.server.getClientManager().handleLogOut(this.clientId);
} }
try { try {
if (!this.socket.isClosed()) { if (!this.socket.isClosed()) {
@ -140,7 +140,7 @@ public class ClientThread extends Thread {
try { try {
var msg = this.server.getSerializer().readMessage(this.in); var msg = this.server.getSerializer().readMessage(this.in);
if (msg instanceof Identification id) { if (msg instanceof Identification id) {
this.server.getClientManager().registerClient(id, this); this.server.getClientManager().handleLogIn(id, this);
return true; return true;
} }
} catch (IOException e) { } catch (IOException e) {

View File

@ -1,17 +0,0 @@
package nl.andrewl.concord_server.event;
import nl.andrewl.concord_core.msg.types.ChannelUsersRequest;
import nl.andrewl.concord_core.msg.types.ChannelUsersResponse;
import nl.andrewl.concord_server.client.ClientThread;
import nl.andrewl.concord_server.ConcordServer;
public class ChannelUsersRequestHandler implements MessageHandler<ChannelUsersRequest> {
@Override
public void handle(ChannelUsersRequest msg, ClientThread client, ConcordServer server) throws Exception {
var optionalChannel = server.getChannelManager().getChannelById(msg.getChannelId());
if (optionalChannel.isPresent()) {
var channel = optionalChannel.get();
client.sendToClient(new ChannelUsersResponse(channel.getUserData()));
}
}
}

View File

@ -1,9 +0,0 @@
package nl.andrewl.concord_server.event;
import nl.andrewl.concord_core.msg.types.Chat;
import nl.andrewl.concord_server.client.ClientThread;
import nl.andrewl.concord_server.ConcordServer;
public interface EventListener {
default void chatMessageReceived(ConcordServer server, Chat chat, ClientThread client) {}
}

View File

@ -4,6 +4,11 @@ import nl.andrewl.concord_core.msg.Message;
import nl.andrewl.concord_server.client.ClientThread; import nl.andrewl.concord_server.client.ClientThread;
import nl.andrewl.concord_server.ConcordServer; import nl.andrewl.concord_server.ConcordServer;
/**
* Defines a component which can handle messages of a certain type which were
* received from a client.
* @param <T> The type of message to be handled.
*/
public interface MessageHandler<T extends Message> { public interface MessageHandler<T extends Message> {
void handle(T msg, ClientThread client, ConcordServer server) throws Exception; void handle(T msg, ClientThread client, ConcordServer server) throws Exception;
} }

View File

@ -1,3 +0,0 @@
package nl.andrewl.concord_server.util;
public record Pair<A, B>(A first, B second) {}