diff --git a/.gitignore b/.gitignore index 5ff6309..1ef859a 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,7 @@ build/ .vscode/ ### Mac OS ### -.DS_Store \ No newline at end of file +.DS_Store + +# autopeerer +/config/ diff --git a/config/nodes/node1.properties b/config/nodes/node1.properties new file mode 100644 index 0000000..6ec89e4 --- /dev/null +++ b/config/nodes/node1.properties @@ -0,0 +1,3 @@ +key=AAAAAAAAAAA= +name="Node Number One" +host=example.com \ No newline at end of file diff --git a/src/main/java/eu/m724/autopeerer/client/MyWebsocketClient.java b/src/main/java/eu/m724/autopeerer/client/MyWebsocketClient.java index f39061e..039b76e 100644 --- a/src/main/java/eu/m724/autopeerer/client/MyWebsocketClient.java +++ b/src/main/java/eu/m724/autopeerer/client/MyWebsocketClient.java @@ -1,5 +1,7 @@ package eu.m724.autopeerer.client; +import eu.m724.autopeerer.common.packet.Packets; +import eu.m724.autopeerer.common.packet.client.LoginPacket; import org.java_websocket.client.WebSocketClient; import org.java_websocket.handshake.ServerHandshake; @@ -26,6 +28,8 @@ public class MyWebsocketClient extends WebSocketClient { public void onOpen(ServerHandshake serverHandshake) { double connectTime = (System.nanoTime() - connectStart) / 1000000.0; System.out.printf("Connected in %.3f ms\n", connectTime); + + Packets.send(new LoginPacket(new byte[8]), this::send); } @Override diff --git a/src/main/java/eu/m724/autopeerer/client/PacketHandler.java b/src/main/java/eu/m724/autopeerer/client/PacketHandler.java index 06d5726..1225ada 100644 --- a/src/main/java/eu/m724/autopeerer/client/PacketHandler.java +++ b/src/main/java/eu/m724/autopeerer/client/PacketHandler.java @@ -55,16 +55,14 @@ public class PacketHandler { var status = PingResponsePacket.PingResponseStatus.ERROR; try { - Process process = Runtime.getRuntime().exec(new String[] { "ping", "-3Ac", "10", packet.target.getHostAddress() }); + Process process = Runtime.getRuntime().exec(new String[] { "ping", "-3Ac", "10", "-W", "1", packet.target.getHostAddress() }); try (BufferedReader reader = process.inputReader()) { for (String l : reader.lines().toList()) { - boolean error = l.startsWith("From"); // indicates an error boolean end = l.startsWith("rtt"); - if (error) { + if (l.startsWith("PING")) { status = PingResponsePacket.PingResponseStatus.UNREACHABLE; - break; } if (end) { diff --git a/src/main/java/eu/m724/autopeerer/common/packet/Packets.java b/src/main/java/eu/m724/autopeerer/common/packet/Packets.java index ab757d1..fd3594b 100644 --- a/src/main/java/eu/m724/autopeerer/common/packet/Packets.java +++ b/src/main/java/eu/m724/autopeerer/common/packet/Packets.java @@ -1,7 +1,9 @@ package eu.m724.autopeerer.common.packet; +import eu.m724.autopeerer.common.packet.client.LoginPacket; import eu.m724.autopeerer.common.packet.client.PingRequestPacket; import eu.m724.autopeerer.common.packet.client.SessionRequestPacket; +import eu.m724.autopeerer.common.packet.server.LoginResponsePacket; import eu.m724.autopeerer.common.packet.server.PingResponsePacket; import eu.m724.autopeerer.common.packet.server.SessionResponsePacket; @@ -14,7 +16,9 @@ public class Packets { Packet packet = null; - if (id == 1) { + if (id == 0) { + packet = LoginResponsePacket.deserialize(buffer); + } else if (id == 1) { packet = PingRequestPacket.deserialize(buffer); } else if (id == 2) { packet = SessionRequestPacket.deserialize(buffer); @@ -28,7 +32,9 @@ public class Packets { Packet packet = null; - if (id == 1) { + if (id == 0) { + packet = LoginPacket.deserialize(buffer); + } else if (id == 1) { packet = PingResponsePacket.deserialize(buffer); } else if (id == 2) { packet = SessionResponsePacket.deserialize(buffer); diff --git a/src/main/java/eu/m724/autopeerer/common/packet/client/LoginPacket.java b/src/main/java/eu/m724/autopeerer/common/packet/client/LoginPacket.java new file mode 100644 index 0000000..31bbfd4 --- /dev/null +++ b/src/main/java/eu/m724/autopeerer/common/packet/client/LoginPacket.java @@ -0,0 +1,33 @@ +package eu.m724.autopeerer.common.packet.client; + +import eu.m724.autopeerer.common.packet.Packet; + +import java.nio.ByteBuffer; + +public class LoginPacket implements Packet { + public final byte[] key; + + public LoginPacket(byte[] key) { + this.key = key; + assert key.length == 8; + } + + @Override + public byte getId() { + return 0; + } + + @Override + public ByteBuffer serialize() { + var buffer = ByteBuffer.allocate(8); + buffer.put(key); + return buffer; + } + + public static LoginPacket deserialize(ByteBuffer buffer) { + var key = new byte[8]; + buffer.get(key); + + return new LoginPacket(key); + } +} diff --git a/src/main/java/eu/m724/autopeerer/common/packet/server/LoginResponsePacket.java b/src/main/java/eu/m724/autopeerer/common/packet/server/LoginResponsePacket.java new file mode 100644 index 0000000..372eb64 --- /dev/null +++ b/src/main/java/eu/m724/autopeerer/common/packet/server/LoginResponsePacket.java @@ -0,0 +1,36 @@ +package eu.m724.autopeerer.common.packet.server; + +import eu.m724.autopeerer.common.packet.Packet; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; + +public class LoginResponsePacket implements Packet { + public final String nodeId; + + public LoginResponsePacket(String nodeId) { + this.nodeId = nodeId; + } + + @Override + public byte getId() { + return 0; + } + + @Override + public ByteBuffer serialize() { + var nodeIdEncoded = nodeId.getBytes(StandardCharsets.UTF_8); + var buffer = ByteBuffer.allocate(1 + nodeIdEncoded.length); + buffer.put((byte) nodeIdEncoded.length); + buffer.put(nodeIdEncoded); + return buffer; + } + + public static LoginResponsePacket deserialize(ByteBuffer buffer) { + var size = Byte.toUnsignedInt(buffer.get()); + var nodeIdEncoded = new byte[size]; + buffer.get(nodeIdEncoded); + + return new LoginResponsePacket(new String(nodeIdEncoded, StandardCharsets.UTF_8)); + } +} diff --git a/src/main/java/eu/m724/autopeerer/server/ClientState.java b/src/main/java/eu/m724/autopeerer/server/ClientState.java index fbf6025..c9ee405 100644 --- a/src/main/java/eu/m724/autopeerer/server/ClientState.java +++ b/src/main/java/eu/m724/autopeerer/server/ClientState.java @@ -8,8 +8,10 @@ import java.nio.ByteBuffer; import java.util.function.Consumer; public class ClientState { + public boolean authenticated = false; public final int clientId; public final WebSocket socket; + Node node; private final Consumer sender; @@ -22,4 +24,8 @@ public class ClientState { void send(Packet packet) { Packets.send(packet, sender); } + + void end() { + socket.close(); + } } diff --git a/src/main/java/eu/m724/autopeerer/server/Main.java b/src/main/java/eu/m724/autopeerer/server/Main.java index 5bb008f..5a0ac47 100644 --- a/src/main/java/eu/m724/autopeerer/server/Main.java +++ b/src/main/java/eu/m724/autopeerer/server/Main.java @@ -1,13 +1,32 @@ package eu.m724.autopeerer.server; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Set; + public class Main { public static void main(String[] args) { System.out.println("Hello world!"); - var packetHandler = new PacketHandler(); - var client = new MyWebsocketServer(packetHandler); + var config = new ServerConfiguration(); + Set nodes; + try { + config.init(); + nodes = config.loadNodes(); + if (nodes.isEmpty()) + System.err.println("Loaded 0 nodes. No client will be able to connect."); + else + System.out.printf("Loaded %d nodes.\n", nodes.size()); + } catch (IOException e) { + throw new RuntimeException("Failed to init configuration", e); + } + + var packetHandler = new PacketHandler(nodes); + var client = new MyWebsocketServer( + new InetSocketAddress(config.getString("listen.address"), config.getInt("listen.port")), + packetHandler + ); - client.setDaemon(false); client.start(); } } diff --git a/src/main/java/eu/m724/autopeerer/server/MyWebsocketServer.java b/src/main/java/eu/m724/autopeerer/server/MyWebsocketServer.java index 7b2b2ec..95fe79a 100644 --- a/src/main/java/eu/m724/autopeerer/server/MyWebsocketServer.java +++ b/src/main/java/eu/m724/autopeerer/server/MyWebsocketServer.java @@ -16,13 +16,15 @@ import java.util.Map; public class MyWebsocketServer extends WebSocketServer { private final PacketHandler packetHandler; + private final Map states = new HashMap<>(); private int id = 0; - public MyWebsocketServer(PacketHandler packetHandler) { - super(new InetSocketAddress("127.0.0.1", 8002)); + public MyWebsocketServer(InetSocketAddress address, PacketHandler packetHandler) { + super(address); this.setReuseAddr(true); + this.packetHandler = packetHandler; } @@ -35,7 +37,8 @@ public class MyWebsocketServer extends WebSocketServer { // TODO testing try { state.send(new PingRequestPacket((short) 1, InetAddress.getByName("1.1.1.1"))); - state.send(new PingRequestPacket((short) 2, InetAddress.getByName("1.1.1.2"))); + state.send(new PingRequestPacket((short) 2, InetAddress.getByName("1.2.3.4"))); + state.send(new PingRequestPacket((short) 3, InetAddress.getByName("1.1.1.2"))); state.send(new SessionRequestPacket( (short) 1, @@ -52,7 +55,8 @@ public class MyWebsocketServer extends WebSocketServer { @Override public void onClose(WebSocket conn, int code, String reason, boolean remote) { var state = states.remove(conn); - System.out.printf("[%d] Disconnected: %s\n", state.clientId, conn.getRemoteSocketAddress().getHostString()); + var id = state.authenticated ? state.node.id() : conn.getRemoteSocketAddress().getHostString(); + System.out.printf("[%d] Disconnected: %s\n", state.clientId, id); } @Override diff --git a/src/main/java/eu/m724/autopeerer/server/Node.java b/src/main/java/eu/m724/autopeerer/server/Node.java new file mode 100644 index 0000000..876a21d --- /dev/null +++ b/src/main/java/eu/m724/autopeerer/server/Node.java @@ -0,0 +1,20 @@ +package eu.m724.autopeerer.server; + +import java.util.Base64; +import java.util.Properties; + +public record Node( + String id, + byte[] key, + String name, + String host +) { + static Node fromProperties(String id, Properties properties) { + return new Node( + id, + Base64.getDecoder().decode(properties.getProperty("key")), + properties.getProperty("name"), + properties.getProperty("host") + ); + } +} diff --git a/src/main/java/eu/m724/autopeerer/server/PacketHandler.java b/src/main/java/eu/m724/autopeerer/server/PacketHandler.java index f348d14..feb57c5 100644 --- a/src/main/java/eu/m724/autopeerer/server/PacketHandler.java +++ b/src/main/java/eu/m724/autopeerer/server/PacketHandler.java @@ -2,14 +2,21 @@ package eu.m724.autopeerer.server; import eu.m724.autopeerer.common.packet.Packet; import eu.m724.autopeerer.common.packet.Packets; +import eu.m724.autopeerer.common.packet.client.LoginPacket; import eu.m724.autopeerer.common.packet.server.PingResponsePacket; import eu.m724.autopeerer.common.packet.server.SessionResponsePacket; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; -import java.util.Base64; +import java.util.*; public class PacketHandler { + private final Map nodes = new HashMap<>(); + + public PacketHandler(Set nodes) { + nodes.forEach(n -> this.nodes.put(n, null)); + } + void handle(ClientState state, ByteBuffer bytes) { // TODO this is not safe but enough for now Packet p; @@ -29,6 +36,16 @@ public class PacketHandler { throw new RuntimeException(e); } + if (!state.authenticated) { + if (p instanceof LoginPacket packet) { + handleLogin(state, packet); + } else { + System.out.printf("[%d] Sent %s while unauthenticated\n", state.clientId, p.getClass().getName()); + } + + return; + } + if (p instanceof PingResponsePacket packet) { handlePingResponse(state, packet); } else if (p instanceof SessionResponsePacket packet) { @@ -36,6 +53,34 @@ public class PacketHandler { } } + void handleClose(ClientState state) { + nodes.entrySet().stream() + .filter(e -> e.getValue().equals(state)) + .findFirst() + .ifPresent(e -> nodes.remove(e.getKey())); + } + + private void handleLogin(ClientState state, LoginPacket packet) { + var node = nodes.entrySet().stream().filter(e -> Arrays.equals(e.getKey().key(), packet.key)).findFirst().orElse(null); + if (node != null) { + if (node.getValue() != null) { + if (node.getValue().socket.isOpen()) { // if there WAS a connection + System.out.printf("[%d] Tried to log in as %s, but is already logged in\n", state.clientId, node.getKey().id()); + state.end(); + return; + } + } + + System.out.printf("[%d] Logged in as %s\n", state.clientId, node.getKey().id()); + nodes.put(node.getKey(), state); + state.authenticated = true; + state.node = node.getKey(); + } else { + System.out.printf("[%d] Tried to log in with invalid key\n", state.clientId); + state.end(); + } + } + private void handlePingResponse(ClientState state, PingResponsePacket packet) { System.out.printf("[%d] Ping response #%d: %s avg %.3f / mdev %.3f ms\n", state.clientId, packet.requestId, packet.status, packet.average, packet.meanDeviation); } diff --git a/src/main/java/eu/m724/autopeerer/server/ServerConfiguration.java b/src/main/java/eu/m724/autopeerer/server/ServerConfiguration.java new file mode 100644 index 0000000..2543d10 --- /dev/null +++ b/src/main/java/eu/m724/autopeerer/server/ServerConfiguration.java @@ -0,0 +1,69 @@ +package eu.m724.autopeerer.server; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.util.HashSet; +import java.util.Properties; +import java.util.Set; + +public class ServerConfiguration { + private final File directory = new File("config"); + private final File configFile = new File(directory, "server.properties"); + + private final Properties properties = new Properties(); + + public void init() throws IOException { + directory.mkdir(); + + if (!configFile.exists()) { + try (var is = getClass().getClassLoader().getResourceAsStream("server.properties")) { + Files.write(configFile.toPath(), is.readAllBytes()); + properties.load(is); + } + } + + try (var is = new FileInputStream(configFile)) { + properties.load(is); + } + } + + public Set loadNodes() throws IOException { + var nd = new File(directory, "nodes"); + nd.mkdir(); + + var nodes = new HashSet(); + + for (var f : nd.listFiles()) { + var properties = new Properties(); + + try (var is = new FileInputStream(f)) { + properties.load(is); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + + nodes.add(Node.fromProperties(f.getName().split("\\.")[0], properties)); + } + + return nodes; + } + + public String getString(String property) { + return properties.getProperty(property); + } + + public String getString(String property, String def) { + return properties.getProperty(property, def); + } + + public int getInt(String property) { + return Integer.parseInt(getString(property)); + } + + public int getInt(String property, int def) { + return Integer.parseInt(getString(property, String.valueOf(def))); + } +} diff --git a/src/main/resources/server.properties b/src/main/resources/server.properties new file mode 100644 index 0000000..84ea1a0 --- /dev/null +++ b/src/main/resources/server.properties @@ -0,0 +1,2 @@ +listen.address=127.0.0.1 +listen.port=8002 \ No newline at end of file