parent
f8903d841c
commit
1437c720c0
14 changed files with 263 additions and 15 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -35,4 +35,7 @@ build/
|
|||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
|
||||
# autopeerer
|
||||
/config/
|
||||
|
|
3
config/nodes/node1.properties
Normal file
3
config/nodes/node1.properties
Normal file
|
@ -0,0 +1,3 @@
|
|||
key=AAAAAAAAAAA=
|
||||
name="Node Number One"
|
||||
host=example.com
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<LoginPacket> {
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -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<LoginResponsePacket> {
|
||||
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));
|
||||
}
|
||||
}
|
|
@ -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<ByteBuffer> sender;
|
||||
|
||||
|
@ -22,4 +24,8 @@ public class ClientState {
|
|||
void send(Packet<?> packet) {
|
||||
Packets.send(packet, sender);
|
||||
}
|
||||
|
||||
void end() {
|
||||
socket.close();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Node> 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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,13 +16,15 @@ import java.util.Map;
|
|||
|
||||
public class MyWebsocketServer extends WebSocketServer {
|
||||
private final PacketHandler packetHandler;
|
||||
|
||||
private final Map<WebSocket, ClientState> 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
|
||||
|
|
20
src/main/java/eu/m724/autopeerer/server/Node.java
Normal file
20
src/main/java/eu/m724/autopeerer/server/Node.java
Normal file
|
@ -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")
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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<Node, ClientState> nodes = new HashMap<>();
|
||||
|
||||
public PacketHandler(Set<Node> 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);
|
||||
}
|
||||
|
|
|
@ -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<Node> loadNodes() throws IOException {
|
||||
var nd = new File(directory, "nodes");
|
||||
nd.mkdir();
|
||||
|
||||
var nodes = new HashSet<Node>();
|
||||
|
||||
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)));
|
||||
}
|
||||
}
|
2
src/main/resources/server.properties
Normal file
2
src/main/resources/server.properties
Normal file
|
@ -0,0 +1,2 @@
|
|||
listen.address=127.0.0.1
|
||||
listen.port=8002
|
Loading…
Reference in a new issue