package net.yura.lobby.server;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.Cipher;
import net.yura.grasshopper.BugManager;
import net.yura.grasshopper.ReallySimpleFormatter;
import net.yura.lobby.client.ProtoAccess;
import net.yura.lobby.client.ProtoAccess.ObjectProvider;
import net.yura.lobby.database.GameRoom;
import net.yura.lobby.database.User;
import net.yura.lobby.gen.ProtoLobby;
import net.yura.lobby.model.Game;
import net.yura.lobby.model.GameType;
import net.yura.lobby.model.Message;
import net.yura.lobby.model.Player;
/**
* @author Yura Mamyrin
*/
public class LobbyServer implements ObjectProvider {
public static final Logger logger = Logger.getLogger(LobbyServer.class.getName());
private static final int PROTOCOL_VERSION = 1;
public static String[] args;
public static final String NEW_HOST_ARG = "--newHost=";
final ProtoAccess access = new ProtoAccess(this);
public final GameLobby lobby;
public LobbyServer(SocketServer sserver) {
lobby = new GameLobby(sserver);
for (String arg:args) {
if (arg.startsWith(NEW_HOST_ARG)) {
lobby.setNewHost( arg.substring( NEW_HOST_ARG.length() ) );
}
}
}
public void handleMessage(LobbySession session, Message msg) {
String command = msg.getCommand();
Object param = msg.getParam();
if (msg.getWait()!=null) {
try {
Thread.sleep( msg.getWait() );
}
catch (InterruptedException in) {
Thread.currentThread().interrupt();
}
}
logger.info("Got "+session+" "+msg);
try {
if (ProtoAccess.REQUEST_HELLO.equals(command)) {
Map request = (Map)param;
Integer version = (Integer)request.get("version");
String appName = (String)request.get("appName");
String appVersion = (String)request.get("appVersion");
session.setClientVersion(appName+" "+appVersion);
if (version.intValue() == PROTOCOL_VERSION) {
String uuid = (String)request.get("uuid");
lobby.connected( session, uuid, getEncodedPublicKey() );
}
else {
throw new RuntimeException("version miss match, server="+PROTOCOL_VERSION+" client="+version);
}
}
else if (ProtoAccess.REQUEST_LOGIN.equals(command)) {
throw new UnsupportedOperationException("Not supported yet.");
}
else if (ProtoAccess.REQUEST_REGISTER.equals(command)) {
throw new UnsupportedOperationException("Not supported yet.");
}
else if (ProtoAccess.REQUEST_LOGOUT.equals(command)) {
throw new UnsupportedOperationException("Not supported yet.");
}
else if (ProtoAccess.REQUEST_SET_NICK.equals(command)) {
Map request = (Map)param;
Object uobj = request.get("username");
String username = uobj instanceof String?(String)uobj:decrypt( (byte[])uobj );
lobby.setNick(session, username);
}
else if (ProtoAccess.REQUEST_ALL_GAMETYPES.equals(command)) {
lobby.sendGameTypes(session);
}
else if (ProtoAccess.REQUEST_GET_GAMES.equals(command)) {
Map map = (Map)param;
lobby.setCurrentGameType(session, (Integer)map.get("game_type_id") );
}
else if (ProtoLobby.REQUEST_CREATE_NEW_GAME.equals(command)) {
Game game = (Game)param;
if (game.getId() >= 0) {
assertAmMod(session); // only admin can edit existing games.
try {
lobby.database.startTransaction();
GameRoom gameRoom = lobby.database.getGame(game.getId());
if (!gameRoom.getName().equals(game.getName())) {
gameRoom.setName( game.getName() );
lobby.database.saveGame(gameRoom);
lobby.gameChanged(gameRoom);
}
// TODO we may want to resign/add players
}
finally {
lobby.database.endTransaction();
}
}
else {
if (game.getPlayers().isEmpty()) {
GameRoom gameRoom = lobby.createGame(game);
lobby.joinGame(session.getUsername(), gameRoom.getId());
}
else {
Set<Player> players = game.getPlayers();
game.setPlayers(new HashSet());
GameRoom gameRoom = lobby.createGame(game);
// TODO somehow need to tell the DB this is a private game
for (Player player: players) {
gameRoom = lobby.joinGame(player.getName(), gameRoom.getId(), session);
}
// tell everyone about the game.
lobby.gameChanged(gameRoom);
}
}
}
else if (ProtoLobby.REQUEST_DEL_GAME.equals(command)) {
assertAmMod(session);
Map map = (Map)param;
lobby.removeGame((Integer) map.get("game_id"));
}
else if (ProtoLobby.REQUEST_RESIGN_FROM_ALL_GAMES.equals(command)) {
assertAmMod(session);
Map map = (Map)param;
lobby.resignFromAllGames((String) map.get("username"));
}
else if (ProtoLobby.REQUEST_RENAME_USER.equals(command)) {
assertAmMod(session);
Map map = (Map)param;
lobby.setNick((String) map.get("oldName"), (String) map.get("newName"));
}
else if (ProtoLobby.REQUEST_JOIN_GAME.equals(command)) {
Map map = (Map)param;
lobby.joinGame(session.getUsername(), (Integer)map.get("game_id") );
}
else if (ProtoLobby.REQUEST_LEAVE_GAME.equals(command)) {
Map map = (Map)param;
lobby.leaveGame(session.getUsername(), (Integer)map.get("game_id") );
}
else if (ProtoLobby.REQUEST_PLAY_GAME.equals(command)) {
Map map = (Map)param;
lobby.playOrWatch(session, (Integer)map.get("game_id") );
}
else if (ProtoLobby.REQUEST_CLOSE_GAME.equals(command)) {
Map map = (Map)param;
lobby.stopPlayOrWatch(session, (Integer)map.get("game_id") );
}
else if (ProtoLobby.REQUEST_GAME_MESSAGE.equals(command)) {
Map map = (Map)param;
lobby.messageForGame( (Integer)map.get("game_id"), session.getUsername(), map.get("message") );
}
else if (ProtoLobby.COMMAND_CHAT_MESSAGE.equals(command)) {
Map map = (Map)param;
lobby.sendChatMessage( (Integer)map.get("room_id"), session.getUsername(), (String)map.get("message") );
}
else if (ProtoLobby.REQUEST_ANDROID_REGISTER.equals(command)) {
lobby.registerGCM(session,(String)param);
}
else if (ProtoLobby.REQUEST_ANDROID_UNREGISTER.equals(command)) {
lobby.unregisterGCM(session, (String)param );
}
else {
throw new IllegalArgumentException("unknown command "+command);
}
}
catch (Exception ex) {
lobby.send( session,ProtoAccess.COMMAND_SERVER_ERROR, ex.toString() );
logger.log(Level.WARNING, "error dealing with "+session+" "+command+" "+param, ex);
}
}
public void gotConnected(LobbySession ch) {
}
public void lostConnection(LobbySession ch) {
lobby.disconnected(ch);
}
public void closingConnection(LobbySession ch) {
lobby.disconnected(ch);
}
public Message decode(InputStream in,int size) throws IOException {
return (Message)access.load(in, size);
}
public void encode(OutputStream out, Message message) throws IOException {
int size = access.computeAnonymousObjectSize(message);
DataOutputStream dout = new DataOutputStream(out);
dout.writeInt(size);
access.save(out, message);
}
@Override
public Object getObjectId(Object object) {
if (object instanceof GameType) {
return new Integer( ((GameType)object).getId() );
}
throw new IllegalArgumentException("what is this object? "+object );
}
@Override
public Object getObjetById(Object id, Class clas) {
if (clas == GameType.class) {
return lobby.getGameType( ((Integer)id).intValue() );
}
throw new IllegalArgumentException("what is this class? "+clas+" "+id);
}
private static KeyPairGenerator kpg;
private static RSAPrivateKey privKey;
private static RSAPublicKey pubKey;
private static byte[] encodedPubKey;
private static Cipher decrypt;
private static final String cipher = "RSA";
static {
try {
kpg = KeyPairGenerator.getInstance(cipher);
decrypt = Cipher.getInstance(cipher);
kpg.initialize(1024);
generateNewKeys();
}
catch (Exception e) {
e.printStackTrace();
}
}
static byte[] getEncodedPublicKey() {
return encodedPubKey;
}
static synchronized String decrypt(byte[] message) {
try {
return new String( decrypt.doFinal(message) ,"UTF-8");
}
catch (Exception e) {
throw new RuntimeException("failed to decrypt",e);
}
}
private static void generateNewKeys() {
KeyPair kp = kpg.generateKeyPair();
privKey = (RSAPrivateKey)kp.getPrivate();
pubKey = (RSAPublicKey)kp.getPublic();
//LobbyServer.log(pubKey.toString(),Level.INFO);
encodedPubKey = pubKey.getEncoded();
try {
decrypt.init(Cipher.DECRYPT_MODE,privKey);
} catch(Exception e) {
e.printStackTrace();
}
}
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
public static void setupLogging() throws IOException {
// intercept System.out.and System.err to java.util.logging
BugManager.intercept();
//setup logger to log to file
File log = new File("./log/");
if(!log.canRead()) {
if (!log.mkdir()) {
throw new IllegalStateException("can not mkdir "+log);
}
}
else {
File old = new File(log,"old");
if(!old.canRead()) {
if (!old.mkdir()) {
throw new IllegalStateException("can not mkdir "+old);
}
}
else {
// del all old log files
for(File file: old.listFiles()) {
if (file.isFile()) {
file.delete();
}
}
}
for(File file: log.listFiles()) {
if (file.isFile()) {
File oldFile = new File(old,file.getName());
if (!file.renameTo(oldFile)) {
throw new IllegalStateException("can not rename "+file+" to "+oldFile);
}
}
}
}
Logger logger = Logger.getLogger("");
int fileSize = 1024*1024*100; // 100 MB
FileHandler txtLog = new FileHandler("log/LobbyServer.%g.log",fileSize,100,true);
//txtLog.setFormatter(new org.quickserver.util.logging.MicroFormatter());
txtLog.setFormatter( new ReallySimpleFormatter() );
logger.addHandler(txtLog);
// log SQL
//Logger.getLogger("DataNucleus.Datastore.Native").setLevel(Level.FINEST);
}
private void assertAmMod(LobbySession session) {
try {
lobby.database.startTransaction();
User me = lobby.database.getUser(session.getUsername());
if (me.getType() < Player.PLAYER_MODERATOR) {
throw new SecurityException("what do you think you are doing!");
}
}
finally {
lobby.database.endTransaction();
}
}
}