Skip to content

Commit

Permalink
Adding Habilink feature
Browse files Browse the repository at this point in the history
  • Loading branch information
ssalevan committed May 30, 2017
1 parent 8f7a540 commit e0ff6ac
Show file tree
Hide file tree
Showing 11 changed files with 427 additions and 57 deletions.
2 changes: 1 addition & 1 deletion package
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ mvn install:install-file \
-Dpackaging=jar \
-DgeneratePom=true

mvn package
mvn clean package
147 changes: 147 additions & 0 deletions src/main/java/org/jbrain/qlink/HabilinkListener.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package org.jbrain.qlink;

/*
Copyright Jim Brain and Brain Innovations, 2005.
This file is part of QLinkServer.
QLinkServer is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
QLinkServer is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QLinkServer; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/

import org.apache.log4j.Logger;
import org.jbrain.qlink.chat.ChatProfile;
import org.jbrain.qlink.chat.Game;
import org.jbrain.qlink.chat.QRoom;
import org.jbrain.qlink.chat.RoomManager;
import org.jbrain.qlink.connection.QConnection;
import org.jbrain.qlink.state.PlayGame;
import org.jbrain.qlink.state.QState;
import org.jbrain.qlink.user.QHandle;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;


public class HabilinkListener extends Thread {
private static Logger _log=Logger.getLogger(HabilinkListener.class);
private int _iPort;
private String _sAddress;
private QLinkServer _server;

class ProxyThread extends Thread {
Socket _socket;

public ProxyThread(QLinkServer server, Socket s) {
_server=server;
_socket=s;
setDaemon(true);
start();
}

public void run() {
InputStream clientInput;
OutputStream clientOutput;
QConnection conn;
QSession session;

try {
clientInput = _socket.getInputStream();
clientOutput = _socket.getOutputStream();

// Waits for a successful login from the thin client.
HabilinkProxy proxy = new HabilinkProxy(clientInput, clientOutput);
String username = proxy.login();

// Thin client now exec()s the original Habitat client, so rigs up a QuantumLink environment and
// protocol for the logged-in Habitat user.
if (username != null) {
_log.debug("Habilink user '" + username + "' logged in; starting QConnection/QSession");
conn=new QConnection(
clientInput, clientOutput, _server, username, QConnection.SEQ_LOW, QConnection.SEQ_LOW);

QHandle qHandle = new QHandle(username);

session=new QSession(_server, conn, qHandle);
_server.addSession(session);

ChatProfile profile = new ChatProfile();

QRoom qRoom = RoomManager.getRoomManager().join(qHandle, profile);
Game game = qRoom.createGame(0, "Neohabitat", "Neohabitat", false);

session.setState(new PlayGame(session, game, session.getState()));
conn.setSession(session);

conn.init();
_server.addSession(session);
} else {
_log.error("Unable to find username for Habilink connection");
}
} catch (IOException e) {
_log.error(e);
}

}
}

/**
* @param server
* @param port
*/
public HabilinkListener(QLinkServer server, String address, int port) {
_iPort=port;
_sAddress=address;
_server=server;
start();
}

public void run() {
int rc=0;
ArrayList servers=new ArrayList();
QConnection conn;
QSession session;
ServerSocket serverSocket = null;
Socket clientSocket = null;

if(rc==0) {
try {
serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(InetAddress.getByName(_sAddress), _iPort));
} catch (IOException e) {
_log.fatal("Could not listen on port " + _iPort,e);
rc=-1;
}
}
if(rc==0) {
try {
while(true) {
clientSocket = serverSocket.accept();
_log.info("Incoming connection received");
new ProxyThread(_server,clientSocket);
}
} catch (IOException e) {
_log.fatal("TCP/IP accept failed.",e);
rc=-1;
}
}
_log.info("Terminating TCPListener for port " + _iPort);
}
}
123 changes: 123 additions & 0 deletions src/main/java/org/jbrain/qlink/HabilinkProxy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package org.jbrain.qlink;

/*
Copyright Jim Brain and Brain Innovations, 2005.
This file is part of QLinkServer.
QLinkServer is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
QLinkServer is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QLinkServer; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/

import org.apache.log4j.Logger;

import java.io.*;

import java.util.regex.Matcher;
import java.util.regex.Pattern;


public class HabilinkProxy {
private static Logger _log=Logger.getLogger(HabilinkProxy.class);

private static final Pattern USERNAME_REGEX = Pattern.compile("\"name\":\\s*\"(.*)\"");

private String _username;

private InputStream _clientInput;
private OutputStream _clientOutput;

private UsernameScanningProxyThread _usernameScanner;

class UsernameScanningProxyThread extends Thread {
private BufferedReader clientInputReader;
private HabilinkProxy proxy;

private boolean shutdown;

public UsernameScanningProxyThread(HabilinkProxy proxy, InputStream clientInput) {
try {
clientInputReader = new BufferedReader(new InputStreamReader(clientInput, "ISO-8859-1"));
this.proxy = proxy;
} catch (UnsupportedEncodingException e) {
_log.fatal("US-ASCII is not supported", e);
}
setDaemon(true);
}

public void shutdown() {
shutdown = true;
}

public void run() {
String line;
String username = null;
try {
do {
// Proxies the line to the Neohabitat server.
line=clientInputReader.readLine();

_log.debug("Received Habilink line: " + line);
if(line==null) {
_log.error("Connection prematurely terminated");
} else if (username != null) {
proxy.notifyLogin(username);
return;
} else {
Matcher usernameMatcher = USERNAME_REGEX.matcher(line);
if (usernameMatcher.find()) {
username = usernameMatcher.group(1);
}
}
} while (line != null && !shutdown);
} catch (IOException e) {
_log.error("Received error during packet negotiation",e);
}
}
}

public HabilinkProxy(InputStream clientInput, OutputStream clientOutput) {
_clientInput = clientInput;
_clientOutput = clientOutput;
}

private synchronized void notifyLogin(String username) {
_log.debug("New Habilink login with username: " + username);
_username = username;
this.notifyAll();
}

/**
* @return Username of the logged-in Habitat user.
*/
public synchronized String login() {
_usernameScanner = new UsernameScanningProxyThread(this, _clientInput);
_usernameScanner.start();

// Waits up to 30 seconds to detect a username.
try {
wait(30000);
} catch (InterruptedException e) {
_log.debug("Habilink wait was interrupted");
}
_usernameScanner.shutdown();

if (_username == null) {
_log.warn("Unable to find Habilink username within 60 seconds");
}

return _username;
}

}
62 changes: 45 additions & 17 deletions src/main/java/org/jbrain/qlink/QLinkServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,11 @@

public class QLinkServer {

public static final int DEFAULT_PORT = 5190;
public static final String DEFAULT_HOST = "0.0.0.0";
public static final int DEFAULT_QTCP_PORT = 5190;
public static final String DEFAULT_QTCP_HOST = "0.0.0.0";

public static final int DEFAULT_HABILINK_PORT = 1986;
public static final String DEFAULT_HABILINK_HOST = "0.0.0.0";

private static Logger _log=Logger.getLogger(QLinkServer.class);
private static PropertiesConfiguration _config = null;
Expand Down Expand Up @@ -87,8 +90,9 @@ public void sessionTerminated(TerminationEvent event) {
*/
public void addSession(QSession session) {
if(!_vSessions.contains(session)) {
_log.info("Adding session to session list");
_log.info("Adding session to session list: " + session.getHandle().getKey());
_vSessions.add(session);
_htSessions.put(session.getHandle().getKey(), session);
_newest=new Date();
_iSessionCount++;
session.addEventListener(_listener);
Expand All @@ -99,7 +103,7 @@ public void addSession(QSession session) {
* @param session
*/
public void removeSession(QSession session) {
_log.info("Removing session from session list");
_log.info("Removing session from session list: " + session.getHandle().getKey());
if(session.getHandle()!=null) {
_log.info("Removing '" + session.getHandle() + "' from online user list");
_htSessions.remove(session.getHandle().getKey());
Expand Down Expand Up @@ -220,17 +224,31 @@ private void launch(CommandLine args) {
_log.fatal("Could not initialize DB", e);
System.exit(-1);
}
//
int port = DEFAULT_PORT;
if (args.getOptionValue("port") != null) {
port = Integer.parseInt(args.getOptionValue("port"));

// Creates the QTCP QuantumLink-over-TCP protocol listener.
int qctpPort = DEFAULT_QTCP_PORT;
if (args.getOptionValue("qtcpPort") != null) {
qctpPort = Integer.parseInt(args.getOptionValue("qtcpPort"));
}
String qctpHost = DEFAULT_QTCP_HOST;
if (args.getOptionValue("qtcpHost") != null) {
qctpHost = args.getOptionValue("qtcpHost");
}
_log.info("QTCP protocol listening on " + qctpHost + ":" + qctpPort);
new QTCPListener(this, qctpHost, qctpPort);

// Creates the Habilink protocol listener.
int habilinkPort = DEFAULT_HABILINK_PORT;
if (args.getOptionValue("habilinkPort") != null) {
habilinkPort = Integer.parseInt(args.getOptionValue("habilinkPort"));
}
String host = DEFAULT_HOST;
if (args.getOptionValue("host") != null) {
host = args.getOptionValue("host");
String habilinkHost = DEFAULT_HABILINK_HOST;
if (args.getOptionValue("habilinkHost") != null) {
habilinkHost = args.getOptionValue("habilinkHost");
}
_log.info("Listening on " + host + ":" + port);
new QTCPListener(this, host, port);
_log.info("Habilink protocol listening on " + habilinkHost + ":" + habilinkPort);
new HabilinkListener(this, habilinkHost, habilinkPort);

// at this point, we should load the extensions...
// TODO make extensions flexible.
new RoomAuditor(this);
Expand All @@ -242,17 +260,27 @@ private static CommandLine parseArgs(String[] args) {
.hasArg()
.withDescription("Location of the QLink Reloaded configuration file")
.create("configFile");
Option port = OptionBuilder.withArgName("port")
Option port = OptionBuilder.withArgName("qtcpPort")
.hasArg()
.withDescription("Port to serve QLink Reloaded service on (default 5190)")
.create("port");
Option host = OptionBuilder.withArgName("host")
.create("qtcpPort");
Option host = OptionBuilder.withArgName("qtcpHost")
.hasArg()
.withDescription("Host to serve QLink Reloaded service on (default 0.0.0.0)")
.create("host");
.create("qtcpHost");
Option habilinkPort = OptionBuilder.withArgName("habilinkPort")
.hasArg()
.withDescription("Port to serve Habilink proxy on (default 1986)")
.create("habilinkPort");
Option habilinkHost = OptionBuilder.withArgName("habilinkHost")
.hasArg()
.withDescription("Host to serve Habilink proxy on (default 0.0.0.0)")
.create("habilinkHost");
options.addOption(configFile);
options.addOption(port);
options.addOption(host);
options.addOption(habilinkPort);
options.addOption(habilinkHost);
// create the parser
CommandLineParser parser = new PosixParser();
try {
Expand Down
Loading

0 comments on commit e0ff6ac

Please sign in to comment.