login/register working and Websocket partially implemented

This commit is contained in:
simon 2018-04-27 19:42:53 -04:00
parent bd5f8573e8
commit 3492e133e1
34 changed files with 468 additions and 489 deletions

View File

@ -18,8 +18,6 @@
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="Server" />
<orderEntry type="library" name="Maven: org.java-websocket:Java-WebSocket:1.3.6" level="project" />
<orderEntry type="library" name="Maven: mysql:mysql-connector-java:5.1.42" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-text:1.2" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-lang3:3.7" level="project" />
<orderEntry type="library" name="Maven: org.mongodb:mongo-java-driver:2.10.1" level="project" />

View File

@ -17,8 +17,6 @@
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="Server" />
<orderEntry type="library" name="Maven: org.java-websocket:Java-WebSocket:1.3.6" level="project" />
<orderEntry type="library" name="Maven: mysql:mysql-connector-java:5.1.42" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-text:1.2" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-lang3:3.7" level="project" />
<orderEntry type="library" name="Maven: org.mongodb:mongo-java-driver:2.10.1" level="project" />

View File

@ -20,8 +20,6 @@
<orderEntry type="library" name="Maven: junit:junit:4.10" level="project" />
<orderEntry type="library" name="Maven: org.hamcrest:hamcrest-core:1.1" level="project" />
<orderEntry type="module" module-name="Server" />
<orderEntry type="library" name="Maven: org.java-websocket:Java-WebSocket:1.3.6" level="project" />
<orderEntry type="library" name="Maven: mysql:mysql-connector-java:5.1.42" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-text:1.2" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-lang3:3.7" level="project" />
<orderEntry type="library" name="Maven: org.mongodb:mongo-java-driver:2.10.1" level="project" />

View File

@ -1,14 +1,8 @@
package net.simon987.npcplugin.io;
import com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException;
import net.simon987.server.ServerConfiguration;
import net.simon987.server.game.ControllableUnit;
import net.simon987.server.io.DatabaseManager;
import net.simon987.server.logging.LogManager;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class StatsDatabaseManager extends DatabaseManager {
@ -18,32 +12,7 @@ public class StatsDatabaseManager extends DatabaseManager {
public void saveVaultCompletion(ControllableUnit unit, String dimension) {
Connection connection = getConnection();
try {
PreparedStatement p = connection.prepareStatement("INSERT INTO mar_vault_clear " +
"(username, clear_time, vault_id) VALUES (?,?,?)");
p.setString(1, unit.getParent().getUsername());
p.setInt(2, 0);
p.setString(3, dimension);
int result = p.executeUpdate();
LogManager.LOGGER.fine("Saved vault clear (" + result + " rows changed)");
} catch (MySQLIntegrityConstraintViolationException e) {
LogManager.LOGGER.fine("This vault was already cleared by " + unit.getParent().getUsername());
} catch (SQLException e) {
LogManager.LOGGER.severe(e.getMessage());
e.printStackTrace();
} finally {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}

View File

@ -20,8 +20,6 @@
<orderEntry type="library" name="Maven: junit:junit:4.10" level="project" />
<orderEntry type="library" name="Maven: org.hamcrest:hamcrest-core:1.1" level="project" />
<orderEntry type="module" module-name="Server" />
<orderEntry type="library" name="Maven: org.java-websocket:Java-WebSocket:1.3.6" level="project" />
<orderEntry type="library" name="Maven: mysql:mysql-connector-java:5.1.42" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-text:1.2" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-lang3:3.7" level="project" />
<orderEntry type="library" name="Maven: org.mongodb:mongo-java-driver:2.10.1" level="project" />

View File

@ -16,10 +16,8 @@
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Maven: org.java-websocket:Java-WebSocket:1.3.6" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: junit:junit:4.12" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.hamcrest:hamcrest-core:1.3" level="project" />
<orderEntry type="library" name="Maven: mysql:mysql-connector-java:5.1.42" level="project" />
<orderEntry type="library" name="Maven: com.googlecode.json-simple:json-simple:1.1.1" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-text:1.2" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-lang3:3.7" level="project" />

View File

@ -88,22 +88,12 @@
<version>1.4a</version>
<dependencies>
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.3.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.42</version>
</dependency>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>

View File

@ -14,7 +14,8 @@ import net.simon987.server.game.debug.*;
import net.simon987.server.logging.LogManager;
import net.simon987.server.plugin.PluginManager;
import net.simon987.server.user.User;
import net.simon987.server.webserver.SocketServer;
import net.simon987.server.user.UserManager;
import net.simon987.server.websocket.SocketServer;
import java.io.File;
import java.net.UnknownHostException;
@ -39,12 +40,16 @@ public class GameServer implements Runnable {
private MongoClient mongo = null;
private UserManager userManager;
public GameServer() {
this.config = new ServerConfiguration("config.properties");
try{
mongo = new MongoClient("localhost", 27017);
userManager = new UserManager(mongo);
} catch (UnknownHostException e) {
e.printStackTrace();
}
@ -127,7 +132,7 @@ public class GameServer implements Runnable {
uTime = System.currentTimeMillis() - startTime;
waitTime = config.getInt("tick_length") - uTime;
LogManager.LOGGER.info("Wait time : " + waitTime + "ms | Update time: " + uTime + "ms | " + (int) (((double) uTime / waitTime) * 100) + "% load");
// LogManager.LOGGER.info("Wait time : " + waitTime + "ms | Update time: " + uTime + "ms | " + (int) (((double) uTime / waitTime) * 100) + "% load");
try {
if (waitTime >= 0) {
@ -184,14 +189,12 @@ public class GameServer implements Runnable {
save();
}
socketServer.tick();
// socketServer.tick();
LogManager.LOGGER.info("Processed " + gameUniverse.getWorldCount() + " worlds (" + updatedWorlds +
" updated)");
// LogManager.LOGGER.info("Processed " + gameUniverse.getWorldCount() + " worlds (" + updatedWorlds +
// " updated)");
}
void load() {
LogManager.LOGGER.info("Loading all data from MongoDB");
@ -248,14 +251,12 @@ public class GameServer implements Runnable {
int insertedWorlds = 0;
GameUniverse universe = GameServer.INSTANCE.getGameUniverse();
for (World w : universe.getWorlds()) {
// LogManager.LOGGER.fine("Saving world "+w.getId()+" to mongodb");
insertedWorlds++;
worlds.save(w.mongoSerialise());
// If the world should unload, it is removed from the Universe after having been saved.
//If the world should unload, it is removed from the Universe after having been saved.
if (w.shouldUnload()){
unloaded_worlds++;
// LogManager.LOGGER.fine("Unloading world "+w.getId()+" from universe");
universe.removeWorld(w);
}
}
@ -265,17 +266,15 @@ public class GameServer implements Runnable {
if (!u.isGuest()) {
users.save(u.mongoSerialise());
}
}
BasicDBObject serverObj = new BasicDBObject();
serverObj.put("_id","serverinfo"); // a constant id ensures only one entry is kept and updated, instead of a new entry created every save.
serverObj.put("_id", "serverinfo"); //A constant id ensures only one entry is kept and updated, instead of a new entry created every save.
serverObj.put("time", gameUniverse.getTime());
serverObj.put("nextObjectId", gameUniverse.getNextObjectId());
server.save(serverObj);
LogManager.LOGGER.info(""+insertedWorlds+" worlds saved, "+unloaded_worlds+" unloaded");
LogManager.LOGGER.info("Done!");
LogManager.LOGGER.info("" + insertedWorlds + " worlds saved, " + unloaded_worlds + " unloaded");
} catch (Exception e) {
LogManager.LOGGER.severe("Problem happened during save function");
e.printStackTrace();
@ -297,4 +296,8 @@ public class GameServer implements Runnable {
public DayNightCycle getDayNightCycle() {
return dayNightCycle;
}
public UserManager getUserManager() {
return userManager;
}
}

View File

@ -1,13 +1,15 @@
package net.simon987.server;
import net.simon987.server.logging.LogManager;
import net.simon987.server.webserver.SocketServer;
import net.simon987.server.user.RegistrationException;
import net.simon987.server.web.AlertMessage;
import net.simon987.server.web.AlertType;
import net.simon987.server.websocket.SocketServer;
import org.apache.velocity.app.VelocityEngine;
import spark.ModelAndView;
import spark.Spark;
import spark.template.velocity.VelocityTemplateEngine;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
@ -22,12 +24,6 @@ public class Main {
//Load
GameServer.INSTANCE.load();
SocketServer socketServer = new SocketServer(new InetSocketAddress(config.getString("webSocket_host"),
config.getInt("webSocket_port")), config);
GameServer.INSTANCE.setSocketServer(socketServer);
(new Thread(socketServer)).start();
(new Thread(GameServer.INSTANCE)).start();
//TEST ---------------------------
@ -36,29 +32,100 @@ public class Main {
VelocityTemplateEngine templateEngine = new VelocityTemplateEngine(new VelocityEngine(properties));
//--
//Websocket
Spark.webSocket("/socket", SocketServer.class);
Spark.staticFiles.externalLocation("static");
Spark.get("/", (request, response) -> {
Map<String, Object> model = new HashMap<>();
System.out.println((String) request.session().attribute("user"));
model.put("session", request.session());
return new ModelAndView(model, "home.vm");
}, templateEngine);
Spark.get("/leaderboard", (request, response) -> {
Map<String, Object> model = new HashMap<>();
model.put("session", request.session());
return new ModelAndView(new HashMap<>(), "leaderboard.vm");
}, templateEngine);
Spark.get("/play", (request, response) -> {
Map<String, Object> model = new HashMap<>();
model.put("session", request.session());
return new ModelAndView(model, "play.vm");
}, templateEngine);
Spark.get("/account", (request, response) -> {
Map<String, Object> model = new HashMap<>();
model.put("session", request.session());
if (request.session().attribute("username") != null) {
model.put("user", GameServer.INSTANCE.getGameUniverse().getUser(request.session().attribute("username")));
}
return new ModelAndView(model, "account.vm");
}, templateEngine);
Spark.post("/register", (request, response) -> {
String username = request.queryParams("username");
String password = request.queryParams("password");
if (username != null && password != null) {
try {
GameServer.INSTANCE.getUserManager().registerUser(username, password);
AlertMessage[] messages = {new AlertMessage("Successfully registered", AlertType.SUCCESS)};
request.session().attribute("messages", messages);
request.session().attribute("username", username);
LogManager.LOGGER.fine("(Web) " + username + " registered " + request.ip());
} catch (RegistrationException e) {
AlertMessage[] messages = {new AlertMessage(e.getMessage(), AlertType.DANGER)};
request.session().attribute("messages", messages);
}
}
response.redirect("/account");
return null;
});
Spark.post("/login", (request, response) -> {
String username = request.queryParams("username");
String password = request.queryParams("password");
if (username != null && password != null) {
if (GameServer.INSTANCE.getUserManager().validateUser(username, password)) {
AlertMessage[] messages = {new AlertMessage("Logged in as " + username, AlertType.INFO)};
request.session().attribute("messages", messages);
request.session().attribute("username", username);
LogManager.LOGGER.fine("(Web) " + username + " logged in");
} else {
AlertMessage[] messages = {new AlertMessage("Invalid username or password", AlertType.DANGER)};
request.session().attribute("messages", messages);
}
}
response.redirect("/account");
return null;
});
Spark.get("logout", (request, response) -> {
AlertMessage[] messages = {new AlertMessage("Logged out", AlertType.INFO)};
request.session().attribute("messages", messages);
request.session().removeAttribute("username");
response.redirect("/account");
return null;
});
Spark.after((request, response) -> response.header("Content-Encoding", "gzip"));
}
}

View File

@ -160,7 +160,7 @@ public class CPU implements MongoSerialisable {
}
int elapsed = (int) (System.currentTimeMillis() - startTime);
LogManager.LOGGER.fine(counter + " instruction in " + elapsed + "ms : " + (double) counter / (elapsed / 1000) / 1000000 + "MHz");
// LogManager.LOGGER.fine(counter + " instruction in " + elapsed + "ms : " + (double) counter / (elapsed / 1000) / 1000000 + "MHz");
//Write execution cost and instruction count to memory
@ -173,7 +173,6 @@ public class CPU implements MongoSerialisable {
public void executeInstruction(Instruction instruction, int source, int destination) {
//Execute the instruction
if (source == 0) {
//No operand (assuming that destination is also null)

View File

@ -1,8 +1,10 @@
package net.simon987.server.event;
import net.simon987.server.webserver.OnlineUser;
import net.simon987.server.websocket.OnlineUser;
import org.json.simple.JSONObject;
import java.io.IOException;
public class DebugCommandEvent extends GameEvent {
private JSONObject command;
@ -37,7 +39,11 @@ public class DebugCommandEvent extends GameEvent {
response.put("t", "debug");
response.put("message", message);
((OnlineUser) getSource()).getWebSocket().send(response.toJSONString());
try {
((OnlineUser) getSource()).getWebSocket().getRemote().sendString(response.toJSONString());
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,9 @@
package net.simon987.server.user;
public class RegistrationException extends Exception {
public RegistrationException(String message) {
super(message);
}
}

View File

@ -18,6 +18,7 @@ public class User implements MongoSerialisable {
private String username;
private String userCode;
private String password;
private CPU cpu;
@ -49,6 +50,7 @@ public class User implements MongoSerialisable {
dbObject.put("code", userCode);
dbObject.put("controlledUnit", controlledUnit.getObjectId());
dbObject.put("cpu", cpu.mongoSerialise());
dbObject.put("password", password);
return dbObject;
@ -59,6 +61,7 @@ public class User implements MongoSerialisable {
User user = new User((ControllableUnit) GameServer.INSTANCE.getGameUniverse().getObject((long) obj.get("controlledUnit")));
user.username = (String) obj.get("username");
user.userCode = (String) obj.get("code");
user.password = (String) obj.get("password");
user.getControlledUnit().setParent(user);
@ -107,4 +110,8 @@ public class User implements MongoSerialisable {
public void setGuest(boolean guest) {
this.guest = guest;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@ -1,6 +1,7 @@
package net.simon987.server.user;
import com.mongodb.*;
import net.simon987.server.GameServer;
import net.simon987.server.assembly.exception.CancelledException;
import org.springframework.security.crypto.bcrypt.BCrypt;
@ -34,16 +35,37 @@ public class UserManager {
return userList;
}
public void registerUser(User user, String password) {
public void registerUser(String username, String password) throws RegistrationException {
DBObject dbUser = user.mongoSerialise();
if (username.length() < 5 || username.length() > 20) {
throw new RegistrationException("Username must be 5-20 characters");
}
if (password.length() < 8 || password.length() > 96) {
throw new RegistrationException("Password must be 8-96 characters");
}
String salt = BCrypt.gensalt(12);
String hashedPassword = BCrypt.hashpw(password, salt);
//Check if exists
DBObject where = new BasicDBObject();
where.put("_id", username);
dbUser.put("password", hashedPassword);
if (userCollection.findOne(where) != null) {
throw new RegistrationException("Username is already in use");
}
userCollection.save(dbUser);
try {
User user = GameServer.INSTANCE.getGameUniverse().getOrCreateUser(username, true);
user.setUsername(username);
String salt = BCrypt.gensalt();
String hashedPassword = BCrypt.hashpw(password, salt);
user.setPassword(hashedPassword);
DBObject dbUser = user.mongoSerialise();
userCollection.save(dbUser);
} catch (Exception e) {
throw new RegistrationException("An exception occurred while trying to create user: " + e.getMessage());
}
}
public boolean validateUser(String username, String password) {
@ -52,7 +74,6 @@ public class UserManager {
where.put("_id", username);
DBObject user = userCollection.findOne(where);
return user != null && BCrypt.checkpw(password, (String) user.get("password"));
}
}

View File

@ -0,0 +1,20 @@
package net.simon987.server.web;
public class AlertMessage {
private String message;
private AlertType type;
public AlertMessage(String message, AlertType type) {
this.message = message;
this.type = type;
}
public String getMessage() {
return message;
}
public AlertType getType() {
return type;
}
}

View File

@ -0,0 +1,23 @@
package net.simon987.server.web;
public enum AlertType {
SUCCESS("alert-success"),
INFO("alert-info"),
WARNING("alert-info"),
DANGER("alert-danger"),
PRIMARY("alert-primary"),
SECONDARY("alert-secondary"),
DARK("alert-dark");
public String name;
AlertType(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}

View File

@ -1,10 +0,0 @@
package net.simon987.server.webserver;
import org.java_websocket.exceptions.WebsocketNotConnectedException;
import org.json.simple.JSONObject;
public interface MessageHandler {
void handle(OnlineUser user, JSONObject json) throws WebsocketNotConnectedException;
}

View File

@ -1,323 +0,0 @@
package net.simon987.server.webserver;
import net.simon987.server.GameServer;
import net.simon987.server.ServerConfiguration;
import net.simon987.server.game.ControllableUnit;
import net.simon987.server.logging.LogManager;
import net.simon987.server.user.User;
import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.DefaultSSLWebSocketServerFactory;
import org.java_websocket.server.WebSocketServer;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.xml.bind.DatatypeConverter;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.BindException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
public class SocketServer extends WebSocketServer {
private OnlineUserManager userManager = new OnlineUserManager();
private SocketServerDatabase database;
private MessageDispatcher messageDispatcher = new MessageDispatcher();
public SocketServer(InetSocketAddress address, ServerConfiguration config) {
super(address);
if (config.getInt("use_secure_webSocket") != 0) {
SSLContext context = getContext(config.getString("cert_path"));
if (context != null) {
setWebSocketFactory(new DefaultSSLWebSocketServerFactory(context));
LogManager.LOGGER.info("(WS) Enabled secure webSocket");
} else {
LogManager.LOGGER.severe("(WS) Failed to create SSL context");
}
}
setConnectionLostTimeout(120);
setReuseAddr(true); //To avoid BindException
database = new SocketServerDatabase(config);
messageDispatcher.addHandler(new UserInfoRequestHandler());
messageDispatcher.addHandler(new TerrainRequestHandler());
messageDispatcher.addHandler(new ObjectsRequestHandler());
messageDispatcher.addHandler(new CodeUploadHandler());
messageDispatcher.addHandler(new CodeRequestHandler());
messageDispatcher.addHandler(new KeypressHandler());
messageDispatcher.addHandler(new FloppyHandler());
messageDispatcher.addHandler(new DebugCommandHandler());
}
@Override
public void onOpen(WebSocket conn, ClientHandshake handshake) {
LogManager.LOGGER.info("(WS) New Websocket connection " + conn.getRemoteSocketAddress());
userManager.add(new OnlineUser(conn));
}
@Override
public void onClose(WebSocket conn, int code, String reason, boolean remote) {
LogManager.LOGGER.info("(WS) Closed " + conn.getRemoteSocketAddress() + " with exit code " + code + " additional info: " + reason);
userManager.remove(userManager.getUser(conn));
}
@Override
public void onMessage(WebSocket conn, String message) {
OnlineUser onlineUser = userManager.getUser(conn);
if (onlineUser != null) {
if (onlineUser.isAuthenticated()) {
messageDispatcher.dispatch(onlineUser, message);
} else {
LogManager.LOGGER.info("(WS) Received message from unauthenticated user " + conn.getRemoteSocketAddress());
//We expect a 128 characters long token
if (message.length() == 128) {
String username = database.validateAuthToken(message);
if (username != null) {
User user = GameServer.INSTANCE.getGameUniverse().getOrCreateUser(username, true);
onlineUser.setModerator(database.isModerator(username));
LogManager.LOGGER.info("(WS) User was successfully authenticated: " + user.getUsername() +
" moderator: " + onlineUser.isModerator());
onlineUser.setUser(user);
onlineUser.setAuthenticated(true);
conn.send("{\"t\":\"auth\", \"m\":\"ok\"}");
} else {
User user = GameServer.INSTANCE.getGameUniverse().getOrCreateUser(GameServer.INSTANCE.getGameUniverse().getGuestUsername(), false);
onlineUser.setUser(user);
onlineUser.setAuthenticated(true);
onlineUser.setGuest(true);
LogManager.LOGGER.info("(WS) Created guest user " +
onlineUser.getUser().getUsername() + conn.getRemoteSocketAddress());
conn.send("{\"t\":\"auth\", \"m\":\"ok\"}");
}
}
}
} else {
LogManager.LOGGER.severe("(WS) FIXME: SocketServer:onMessage");
}
}
@Override
public void onMessage(WebSocket conn, ByteBuffer message) {
//System.out.println("received ByteBuffer from " + conn.getRemoteSocketAddress());
}
@Override
public void onError(WebSocket conn, Exception ex) {
if (ex instanceof BindException) {
LogManager.LOGGER.severe("Address already in use");
System.exit(-1);
} else {
LogManager.LOGGER.severe("an error occurred on connection " + conn + ": " + ex);
userManager.remove(userManager.getUser(conn));
ex.printStackTrace();
}
}
@Override
public void onStart() {
LogManager.LOGGER.info("(WS) Server started successfully");
}
/**
* Called every tick
*/
public void tick() {
JSONObject json = new JSONObject();
json.put("t", "tick");
// LogManager.LOGGER.info("Notified " + userManager.getOnlineUsers().size() + " users");
ArrayList<OnlineUser> onlineUsers = new ArrayList<>(userManager.getOnlineUsers()); //Avoid ConcurrentModificationException
for (OnlineUser user : onlineUsers) {
if (user.getWebSocket().isOpen()) {
if (user.isGuest()) {
json.remove("c");
user.getWebSocket().send(json.toJSONString());
} else {
try {
ControllableUnit unit = user.getUser().getControlledUnit();
//Send keyboard updated buffer
ArrayList<Integer> kbBuffer = unit.getKeyboardBuffer();
JSONArray keys = new JSONArray();
keys.addAll(kbBuffer);
json.put("keys", keys);
//Send console buffer
if (unit.getConsoleMessagesBuffer().size() > 0) {
JSONArray buff = new JSONArray();
for (char[] message : unit.getConsoleMessagesBuffer()) {
buff.add(new String(message));
}
json.put("c", buff);
} else {
json.remove("c");
}
json.put("cm", unit.getConsoleMode());
//Send tick message
user.getWebSocket().send(json.toJSONString());
} catch (NullPointerException e) {
//User is online but not completely initialised
}
}
}
}
}
public OnlineUserManager getUserManager() {
return userManager;
}
/**
* See https://github.com/TooTallNate/Java-WebSocket/blob/master/src/main/example/SSLServerLetsEncryptExample.java
*/
/*
* * Copyright (c) 2010-2017 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*/
private static SSLContext getContext(String pathTo) {
SSLContext context;
String password = "MAR";
try {
context = SSLContext.getInstance("TLS");
byte[] certBytes = parseDERFromPEM(getBytes(new File(pathTo + File.separator + "cert.pem")),
"-----BEGIN CERTIFICATE-----", "-----END CERTIFICATE-----");
byte[] keyBytes = parseDERFromPEM(getBytes(new File(pathTo + File.separator + "privkey.pem")),
"-----BEGIN PRIVATE KEY-----", "-----END PRIVATE KEY-----");
X509Certificate cert = generateCertificateFromDER(certBytes);
RSAPrivateKey key = generatePrivateKeyFromDER(keyBytes);
KeyStore keystore = KeyStore.getInstance("JKS");
keystore.load(null);
keystore.setCertificateEntry("cert-alias", cert);
keystore.setKeyEntry("key-alias", key, password.toCharArray(), new Certificate[]{cert});
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(keystore, password.toCharArray());
KeyManager[] km = kmf.getKeyManagers();
context.init(km, null, null);
} catch (Exception e) {
context = null;
}
return context;
}
private static byte[] parseDERFromPEM(byte[] pem, String beginDelimiter, String endDelimiter) {
String data = new String(pem);
String[] tokens = data.split(beginDelimiter);
tokens = tokens[1].split(endDelimiter);
return DatatypeConverter.parseBase64Binary(tokens[0]);
}
private static RSAPrivateKey generatePrivateKeyFromDER(byte[] keyBytes) throws InvalidKeySpecException, NoSuchAlgorithmException {
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory factory = KeyFactory.getInstance("RSA");
return (RSAPrivateKey) factory.generatePrivate(spec);
}
private static X509Certificate generateCertificateFromDER(byte[] certBytes) throws CertificateException {
CertificateFactory factory = CertificateFactory.getInstance("X.509");
return (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(certBytes));
}
private static byte[] getBytes(File file) {
byte[] bytesArray = new byte[(int) file.length()];
FileInputStream fis;
try {
fis = new FileInputStream(file);
fis.read(bytesArray); //read file into bytes[]
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
return bytesArray;
}
}

View File

@ -1,11 +1,13 @@
package net.simon987.server.webserver;
package net.simon987.server.websocket;
import net.simon987.server.logging.LogManager;
import org.json.simple.JSONObject;
import java.io.IOException;
public class CodeRequestHandler implements MessageHandler {
@Override
public void handle(OnlineUser user, JSONObject json) {
public void handle(OnlineUser user, JSONObject json) throws IOException {
if (json.get("t").equals("codeRequest")) {
@ -18,7 +20,7 @@ public class CodeRequestHandler implements MessageHandler {
response.put("t", "code");
response.put("code", "; Create a free account to control your own Cubot with assembly language!"); //todo load from config
user.getWebSocket().send(response.toJSONString());
user.getWebSocket().getRemote().sendString(response.toJSONString());
} else {
@ -27,7 +29,7 @@ public class CodeRequestHandler implements MessageHandler {
response.put("t", "code");
response.put("code", user.getUser().getUserCode());
user.getWebSocket().send(response.toJSONString());
user.getWebSocket().getRemote().sendString(response.toJSONString());
}

View File

@ -1,4 +1,4 @@
package net.simon987.server.webserver;
package net.simon987.server.websocket;
import net.simon987.server.GameServer;
import net.simon987.server.assembly.Assembler;
@ -6,10 +6,12 @@ import net.simon987.server.assembly.AssemblyResult;
import net.simon987.server.logging.LogManager;
import org.json.simple.JSONObject;
import java.io.IOException;
public class CodeUploadHandler implements MessageHandler {
@Override
public void handle(OnlineUser user, JSONObject json) {
public void handle(OnlineUser user, JSONObject json) throws IOException {
if (json.get("t").equals("uploadCode")) {
LogManager.LOGGER.fine("(WS) Code upload from " + user.getUser().getUsername());
@ -48,11 +50,9 @@ public class CodeUploadHandler implements MessageHandler {
response.put("bytes", ar.bytes.length);
response.put("exceptions", ar.exceptions.size());
user.getWebSocket().send(response.toJSONString());
user.getWebSocket().getRemote().sendString(response.toJSONString());
}
}
}
}
}

View File

@ -1,14 +1,13 @@
package net.simon987.server.webserver;
package net.simon987.server.websocket;
import net.simon987.server.GameServer;
import net.simon987.server.event.DebugCommandEvent;
import org.java_websocket.exceptions.WebsocketNotConnectedException;
import org.json.simple.JSONObject;
public class DebugCommandHandler implements MessageHandler {
@Override
public void handle(OnlineUser user, JSONObject json) throws WebsocketNotConnectedException {
public void handle(OnlineUser user, JSONObject json) {
if (json.get("t").equals("debug") && user.isModerator()) {

View File

@ -1,4 +1,4 @@
package net.simon987.server.webserver;
package net.simon987.server.websocket;
import net.simon987.server.GameServer;
import net.simon987.server.logging.LogManager;
@ -21,7 +21,7 @@ public class FloppyHandler implements MessageHandler {
if (user.getUser().getControlledUnit().getFloppyData() != null) {
byte[] bytes = user.getUser().getControlledUnit().getFloppyData().getBytes();
user.getWebSocket().send(bytes);
LogManager.LOGGER.severe("TODO FloppyHandler.handle()");
}

View File

@ -1,4 +1,4 @@
package net.simon987.server.webserver;
package net.simon987.server.websocket;
import org.json.simple.JSONObject;

View File

@ -1,12 +1,12 @@
package net.simon987.server.webserver;
package net.simon987.server.websocket;
import net.simon987.server.logging.LogManager;
import org.java_websocket.exceptions.WebsocketNotConnectedException;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import java.io.IOException;
import java.util.ArrayList;
public class MessageDispatcher {
@ -32,11 +32,8 @@ public class MessageDispatcher {
for (MessageHandler handler : handlers) {
try {
handler.handle(user, json);
} catch (WebsocketNotConnectedException e) {
LogManager.LOGGER.fine("Catched WebsocketNotConnectedException");
} catch (Exception e1) {
LogManager.LOGGER.severe(e1.getMessage());
e1.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
} else {

View File

@ -0,0 +1,11 @@
package net.simon987.server.websocket;
import org.json.simple.JSONObject;
import java.io.IOException;
public interface MessageHandler {
void handle(OnlineUser user, JSONObject json) throws IOException;
}

View File

@ -1,4 +1,4 @@
package net.simon987.server.webserver;
package net.simon987.server.websocket;
import net.simon987.server.GameServer;
import net.simon987.server.game.GameObject;
@ -7,11 +7,13 @@ import net.simon987.server.logging.LogManager;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import java.io.IOException;
public class ObjectsRequestHandler implements MessageHandler {
@Override
public void handle(OnlineUser user, JSONObject json) {
public void handle(OnlineUser user, JSONObject json) throws IOException {
if (json.get("t").equals("object")) {
// LogManager.LOGGER.fine("(WS) Objects request from " + user.getUser().getUsername());
@ -43,7 +45,7 @@ public class ObjectsRequestHandler implements MessageHandler {
if (user.getWebSocket().isOpen()) {
user.getWebSocket().send(response.toJSONString());
user.getWebSocket().getRemote().sendString(response.toJSONString());
}
}
}

View File

@ -1,14 +1,14 @@
package net.simon987.server.webserver;
package net.simon987.server.websocket;
import net.simon987.server.user.User;
import org.java_websocket.WebSocket;
import org.eclipse.jetty.websocket.api.Session;
public class OnlineUser {
private boolean authenticated = false;
private WebSocket webSocket;
private Session webSocket;
private boolean guest;
@ -19,12 +19,12 @@ public class OnlineUser {
*/
private User user;
public OnlineUser(WebSocket webSocket) {
public OnlineUser(Session webSocket) {
this.webSocket = webSocket;
}
public WebSocket getWebSocket() {
public Session getWebSocket() {
return webSocket;
}

View File

@ -1,6 +1,6 @@
package net.simon987.server.webserver;
package net.simon987.server.websocket;
import org.java_websocket.WebSocket;
import org.eclipse.jetty.websocket.api.Session;
import java.util.ArrayList;
@ -12,7 +12,7 @@ public class OnlineUserManager {
private ArrayList<OnlineUser> onlineUsers = new ArrayList<>(10);
public OnlineUser getUser(WebSocket socket) {
public OnlineUser getUser(Session socket) {
ArrayList<OnlineUser> _onlineUsers = new ArrayList<>(onlineUsers);

View File

@ -0,0 +1,129 @@
package net.simon987.server.websocket;
import net.simon987.server.game.ControllableUnit;
import net.simon987.server.logging.LogManager;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import java.io.IOException;
import java.util.ArrayList;
@WebSocket
public class SocketServer {
private OnlineUserManager onlineUserManager = new OnlineUserManager();
private MessageDispatcher messageDispatcher = new MessageDispatcher();
public SocketServer() {
messageDispatcher.addHandler(new UserInfoRequestHandler());
messageDispatcher.addHandler(new TerrainRequestHandler());
messageDispatcher.addHandler(new ObjectsRequestHandler());
messageDispatcher.addHandler(new CodeUploadHandler());
messageDispatcher.addHandler(new CodeRequestHandler());
messageDispatcher.addHandler(new KeypressHandler());
messageDispatcher.addHandler(new FloppyHandler());
messageDispatcher.addHandler(new DebugCommandHandler());
}
@OnWebSocketConnect
public void onOpen(Session session) {
LogManager.LOGGER.info("(WS) New Websocket connection " + session.getRemoteAddress());
onlineUserManager.add(new OnlineUser(session));
}
@OnWebSocketClose
public void onClose(Session session, int code, String reason) {
LogManager.LOGGER.info("(WS) Closed " + session.getRemoteAddress() + " with exit code " + code + " additional info: " + reason);
onlineUserManager.remove(onlineUserManager.getUser(session));
}
@OnWebSocketMessage
public void onMessage(Session session, String message) {
OnlineUser onlineUser = onlineUserManager.getUser(session);
if (onlineUser != null) {
if (onlineUser.isAuthenticated()) {
messageDispatcher.dispatch(onlineUser, message);
} else {
LogManager.LOGGER.info("(WS) Received message from unauthenticated user " + session.getRemoteAddress());
//todo
}
} else {
LogManager.LOGGER.severe("(WS) FIXME: SocketServer:onMessage");
}
}
/**
* Called every tick
*/
public void tick() {
JSONObject json = new JSONObject();
json.put("t", "tick");
ArrayList<OnlineUser> onlineUsers = new ArrayList<>(onlineUserManager.getOnlineUsers()); //Avoid ConcurrentModificationException
for (OnlineUser user : onlineUsers) {
if (user.getWebSocket().isOpen()) {
if (user.isGuest()) {
json.remove("c");
try {
user.getWebSocket().getRemote().sendString((json.toJSONString()));
} catch (IOException e) {
e.printStackTrace();
}
} else {
try {
ControllableUnit unit = user.getUser().getControlledUnit();
//Send keyboard updated buffer
ArrayList<Integer> kbBuffer = unit.getKeyboardBuffer();
JSONArray keys = new JSONArray();
keys.addAll(kbBuffer);
json.put("keys", keys);
//Send console buffer
if (unit.getConsoleMessagesBuffer().size() > 0) {
JSONArray buff = new JSONArray();
for (char[] message : unit.getConsoleMessagesBuffer()) {
buff.add(new String(message));
}
json.put("c", buff);
} else {
json.remove("c");
}
json.put("cm", unit.getConsoleMode());
//Send tick message
user.getWebSocket().getRemote().sendString(json.toJSONString());
} catch (NullPointerException | IOException e) {
e.printStackTrace();
}
}
}
}
}
}

View File

@ -1,4 +1,4 @@
package net.simon987.server.webserver;
package net.simon987.server.websocket;
import net.simon987.server.ServerConfiguration;
import net.simon987.server.io.DatabaseManager;

View File

@ -1,4 +1,4 @@
package net.simon987.server.webserver;
package net.simon987.server.websocket;
import net.simon987.server.GameServer;
import net.simon987.server.game.World;
@ -6,10 +6,12 @@ import net.simon987.server.logging.LogManager;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import java.io.IOException;
public class TerrainRequestHandler implements MessageHandler {
@Override
public void handle(OnlineUser user, JSONObject json) {
public void handle(OnlineUser user, JSONObject json) throws IOException {
if (json.get("t").equals("terrain") && json.containsKey("x") && json.containsKey("y") &&
json.containsKey("dimension")) {
@ -44,14 +46,14 @@ public class TerrainRequestHandler implements MessageHandler {
response.put("terrain", terrain);
response.put("size", world.getWorldSize());
user.getWebSocket().send(response.toJSONString());
user.getWebSocket().getRemote().sendString(response.toJSONString());
} else {
//Uncharted World
JSONObject response = new JSONObject();
response.put("t", "terrain");
response.put("ok", false);
user.getWebSocket().send(response.toJSONString());
user.getWebSocket().getRemote().sendString((response.toJSONString()));
}
}
}

View File

@ -1,15 +1,17 @@
package net.simon987.server.webserver;
package net.simon987.server.websocket;
import net.simon987.server.GameServer;
import net.simon987.server.game.GameObject;
import net.simon987.server.logging.LogManager;
import org.json.simple.JSONObject;
import java.io.IOException;
public class UserInfoRequestHandler implements MessageHandler {
@Override
public void handle(OnlineUser user, JSONObject message) {
public void handle(OnlineUser user, JSONObject message) throws IOException {
if (message.get("t").equals("userInfo")) {
@ -31,8 +33,7 @@ public class UserInfoRequestHandler implements MessageHandler {
json.put("t", "userInfo");
json.put("maxWidth", GameServer.INSTANCE.getGameUniverse().getMaxWidth());
user.getWebSocket().send(json.toJSONString());
user.getWebSocket().getRemote().sendString(json.toJSONString());
}
}

View File

@ -9,47 +9,103 @@
<div class="container">
<div class="card">
<div class="card-header"><h5>Account - login & register</h5></div>
<div class="card-header"><h5>Account</h5></div>
<div class="card-body">
<h5 class="card-title">Login</h5>
<form>
<div class="row">
<div class="col form-group">
<input title="Username" placeholder="Username" name="username" class="form-control">
#if($session.attribute("username"))
## ALREADY LOGGED IN
<p>Logged in as <strong>$session.attribute("username")</strong></p>
<hr>
<h5 class="card-title">Change password</h5>
<form method="post" action="/change_password">
<div class="row">
<div class="col form-group">
<input title="Current password" type="password" placeholder="Current password"
name="password" class="form-control">
</div>
</div>
<div class="col form-group">
<input title="Password" type="password" placeholder="Password" name="password"
class="form-control">
<div class="row">
<div class="col">
<div class="form-group">
<input title="New password" type="password" placeholder="New password"
name="new_password" class="form-control">
</div>
</div>
<div class="col">
<div class="form-group">
<input title="Repeat new password" type="password" placeholder="Repeat new password"
name="new_password" class="form-control">
</div>
</div>
</div>
<button type="submit" class="btn btn-outline-primary text-mono">Register</button>
</form>
<hr>
<h5 class="card-title">Debug information</h5>
<div class="card card-block bg-light text-dark text-mono" style="padding: 1em">
<h5>CPU</h5>
<pre>$user.getCpu()</pre>
<h5>Code</h5>
<pre>$user.getUserCode()</pre>
<h5>Controlled unit</h5>
<p>id: $user.getControlledUnit().getObjectId()</p>
<p>energy: $user.getControlledUnit().getEnergy()</p>
<p>x: $user.getControlledUnit().getX()</p>
<p>y: $user.getControlledUnit().getY()</p>
<p>console mode: $user.getControlledUnit().getConsoleMode()</p>
<p>kbBuffer size: $user.getControlledUnit().getKeyboardBuffer().size()</p>
<h5>World</h5>
<pre>$user.getControlledUnit().getWorld()</pre>
</div>
#else
## NOT LOGGED IN
<h5 class="card-title">Login</h5>
<form method="post" action="/login">
<div class="row">
<div class="col form-group">
<input title="Username" placeholder="Username" name="username" class="form-control">
</div>
<button type="submit" class="btn btn-primary text-mono">Login</button>
</form>
<hr>
<h5 class="card-title">Register</h5>
<form>
<div class="row">
<div class="col form-group">
<input title="Username" placeholder="Username" name="username" class="form-control">
<div class="col form-group">
<input title="Password" type="password" placeholder="Password" name="password"
class="form-control">
</div>
</div>
<div class="col form-group">
<input title="Password" type="password" placeholder="Password" name="password"
class="form-control">
<button type="submit" class="btn btn-primary text-mono">Login</button>
</form>
<hr>
<h5 class="card-title">Register</h5>
<form method="post" action="/register">
<div class="row">
<div class="col form-group">
<input title="Username" placeholder="Username" name="username" class="form-control">
</div>
<div class="col form-group">
<input title="Password" type="password" placeholder="Password" name="password"
class="form-control">
</div>
</div>
</div>
<button type="submit" class="btn btn-outline-primary text-mono">Register</button>
</form>
<button type="submit" class="btn btn-outline-primary text-mono">Register</button>
</form>
#end
</div>
</div>
</div>

View File

@ -29,3 +29,12 @@
</ul>
</div>
</nav>
<div class="container">
#foreach($msg in $session.attribute("messages"))
<div class="alert $msg.getType()"><a href="#" class="close" data-dismiss="alert"
aria-label="close">&times;</a>$msg.getMessage()</div>
#end
$session.removeAttribute("messages")
</div>