Did some cleanup, removed deprecated and unused classes, added javadoc.
This commit is contained in:
parent
80f4cf2035
commit
cd7d40cd7d
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -23,6 +23,26 @@ import java.util.List;
|
|||
* Utility class for handling the establishment of encrypted communication.
|
||||
*/
|
||||
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(
|
||||
InputStream in,
|
||||
OutputStream out,
|
||||
|
|
|
@ -36,7 +36,7 @@ public class Serializer {
|
|||
registerType(3, MoveToChannel.class);
|
||||
registerType(4, ChatHistoryRequest.class);
|
||||
registerType(5, ChatHistoryResponse.class);
|
||||
registerType(6, ChannelUsersRequest.class);
|
||||
// Type id 6 removed due to deprecation.
|
||||
registerType(7, ServerUsers.class);
|
||||
registerType(8, ServerMetaData.class);
|
||||
registerType(9, Error.class);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -77,6 +77,10 @@ public class ChatHistoryRequest implements Message {
|
|||
.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() {
|
||||
String[] pairs = this.query.split(";");
|
||||
if (pairs.length == 0) return Map.of();
|
||||
|
|
|
@ -145,7 +145,7 @@ public class ConcordServer implements Runnable {
|
|||
private void shutdown() {
|
||||
System.out.println("Shutting down the server.");
|
||||
for (var clientId : this.clientManager.getConnectedIds()) {
|
||||
this.clientManager.deregisterClient(clientId);
|
||||
this.clientManager.handleLogOut(clientId);
|
||||
}
|
||||
this.scheduledExecutorService.shutdown();
|
||||
this.executorService.shutdown();
|
||||
|
@ -161,13 +161,7 @@ public class ConcordServer implements Runnable {
|
|||
public void run() {
|
||||
this.running = true;
|
||||
this.scheduledExecutorService.scheduleAtFixedRate(this.discoveryServerPublisher::publish, 0, 1, TimeUnit.MINUTES);
|
||||
StringBuilder startupMessage = new StringBuilder();
|
||||
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);
|
||||
System.out.printf("Opened server on port %d.\n", config.getPort());
|
||||
while (this.running) {
|
||||
try {
|
||||
Socket socket = this.serverSocket.accept();
|
||||
|
|
|
@ -21,11 +21,14 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
* clients in a server.
|
||||
*/
|
||||
@Getter
|
||||
public class Channel {
|
||||
public class Channel implements Comparable<Channel> {
|
||||
private final ConcordServer server;
|
||||
private final UUID id;
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* The set of clients that are connected to this channel.
|
||||
*/
|
||||
private final Set<ClientThread> connectedClients;
|
||||
|
||||
/**
|
||||
|
@ -94,6 +97,10 @@ public class Channel {
|
|||
return users;
|
||||
}
|
||||
|
||||
public String getAsTag() {
|
||||
return "#" + this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
@ -111,4 +118,9 @@ public class Channel {
|
|||
public String toString() {
|
||||
return this.name + " (" + this.id + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Channel o) {
|
||||
return this.getName().compareTo(o.getName());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
package nl.andrewl.concord_server.cli;
|
||||
|
||||
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.RemoveChannelCommand;
|
||||
import nl.andrewl.concord_server.cli.command.StopCommand;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
|
@ -12,6 +11,10 @@ import java.util.Arrays;
|
|||
import java.util.HashMap;
|
||||
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 {
|
||||
private final ConcordServer server;
|
||||
private final Map<String, ServerCliCommand> commands;
|
||||
|
@ -21,8 +24,7 @@ public class ServerCli implements Runnable {
|
|||
this.commands = new HashMap<>();
|
||||
|
||||
this.commands.put("list-clients", new ListClientsCommand());
|
||||
this.commands.put("add-channel", new AddChannelCommand());
|
||||
this.commands.put("remove-channel", new RemoveChannelCommand());
|
||||
this.commands.put("channel", new ChannelCommand());
|
||||
this.commands.put("stop", new StopCommand());
|
||||
|
||||
this.commands.put("help", (s, args) -> {
|
||||
|
@ -35,7 +37,7 @@ public class ServerCli implements Runnable {
|
|||
|
||||
@Override
|
||||
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));
|
||||
String line;
|
||||
try {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -43,10 +43,15 @@ public class ClientManager {
|
|||
* 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
|
||||
* 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 clientThread The client manager thread.
|
||||
*/
|
||||
public void registerClient(Identification identification, ClientThread clientThread) {
|
||||
public void handleLogIn(Identification identification, ClientThread clientThread) {
|
||||
ClientConnectionData data;
|
||||
try {
|
||||
data = identification.getSessionToken() == null ? getNewClientData(identification) : getClientDataFromDb(identification);
|
||||
|
@ -78,7 +83,7 @@ public class ClientManager {
|
|||
* they're currently in.
|
||||
* @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);
|
||||
if (client != null) {
|
||||
client.getCurrentChannel().removeClient(client);
|
||||
|
|
|
@ -112,7 +112,7 @@ public class ClientThread extends Thread {
|
|||
}
|
||||
|
||||
if (this.clientId != null) {
|
||||
this.server.getClientManager().deregisterClient(this.clientId);
|
||||
this.server.getClientManager().handleLogOut(this.clientId);
|
||||
}
|
||||
try {
|
||||
if (!this.socket.isClosed()) {
|
||||
|
@ -140,7 +140,7 @@ public class ClientThread extends Thread {
|
|||
try {
|
||||
var msg = this.server.getSerializer().readMessage(this.in);
|
||||
if (msg instanceof Identification id) {
|
||||
this.server.getClientManager().registerClient(id, this);
|
||||
this.server.getClientManager().handleLogIn(id, this);
|
||||
return true;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {}
|
||||
}
|
|
@ -4,6 +4,11 @@ import nl.andrewl.concord_core.msg.Message;
|
|||
import nl.andrewl.concord_server.client.ClientThread;
|
||||
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> {
|
||||
void handle(T msg, ClientThread client, ConcordServer server) throws Exception;
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
package nl.andrewl.concord_server.util;
|
||||
|
||||
public record Pair<A, B>(A first, B second) {}
|
Loading…
Reference in New Issue