mirror of
https://bitbucket.org/projectswg/forwarder.git
synced 2026-01-16 23:04:26 -05:00
Revamped Forwarder
This commit is contained in:
48
build.gradle
48
build.gradle
@@ -1,30 +1,19 @@
|
||||
apply plugin: 'application'
|
||||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
apply plugin: 'java'
|
||||
|
||||
mainClassName = 'com.projectswg.Forwarder'
|
||||
|
||||
manifest {
|
||||
attributes 'Main-Class': 'com.projectswg.Forwarder'
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'idea'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDirs = ['src']
|
||||
includes ['**/*.java']
|
||||
}
|
||||
}
|
||||
sourceCompatibility = 1.8
|
||||
targetCompatibility = 1.8
|
||||
|
||||
jar {
|
||||
baseName = "Forwarder"
|
||||
classifier = null
|
||||
version = null
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
archiveName = "Forwarder.jar"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':PSWGCommon')
|
||||
compile project(':ClientHolocore')
|
||||
compileOnly fileTree(dir: 'lib', include: ['*.jar'])
|
||||
task wrapper(type: Wrapper) {
|
||||
gradleVersion = "4.6"
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -32,14 +21,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
}
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
maven { url 'https://plugins.gradle.org/m2/' }
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.0'
|
||||
}
|
||||
compile project(':PSWGCommon')
|
||||
compile project(':ClientHolocore')
|
||||
compileOnly fileTree(dir: 'lib', include: ['*.jar'])
|
||||
}
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
package com.projectswg;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import com.projectswg.common.concurrency.Delay;
|
||||
import com.projectswg.common.control.IntentManager;
|
||||
import com.projectswg.common.control.Manager;
|
||||
import com.projectswg.common.debug.Log;
|
||||
import com.projectswg.common.network.packets.swg.ErrorMessage;
|
||||
import com.projectswg.connection.ServerConnectionChangedReason;
|
||||
import com.projectswg.connection.ServerConnectionStatus;
|
||||
import com.projectswg.intents.ClientConnectionChangedIntent;
|
||||
import com.projectswg.intents.ClientToServerPacketIntent;
|
||||
import com.projectswg.intents.ServerConnectionChangedIntent;
|
||||
import com.projectswg.intents.ServerToClientPacketIntent;
|
||||
import com.projectswg.networking.NetInterceptor.InterceptorProperties;
|
||||
import com.projectswg.networking.client.ClientConnectionService;
|
||||
import com.projectswg.networking.server.ServerConnectionService;
|
||||
import com.projectswg.networking.soe.Disconnect.DisconnectReason;
|
||||
import com.projectswg.services.PacketRecordingService;
|
||||
|
||||
public class Connections extends Manager {
|
||||
|
||||
public static final String VERSION = "0.9.8";
|
||||
|
||||
private final ServerConnectionService server;
|
||||
private final ClientConnectionService client;
|
||||
private final PacketRecordingService recording;
|
||||
private final AtomicLong serverToClient;
|
||||
private final AtomicLong clientToServer;
|
||||
private ConnectionCallback callback;
|
||||
private InetAddress addr;
|
||||
private int port;
|
||||
|
||||
public Connections() {
|
||||
this(InetAddress.getLoopbackAddress(), 44463, 44453, true);
|
||||
}
|
||||
|
||||
public Connections(InetAddress remoteAddr, int remotePort, int loginPort, boolean timeout) {
|
||||
setIntentManager(new IntentManager(Runtime.getRuntime().availableProcessors()));
|
||||
this.server = new ServerConnectionService(remoteAddr, remotePort);
|
||||
this.client = new ClientConnectionService(loginPort, timeout);
|
||||
this.recording = new PacketRecordingService();
|
||||
this.serverToClient = new AtomicLong(0);
|
||||
this.clientToServer = new AtomicLong(0);
|
||||
this.callback = null;
|
||||
this.addr = null;
|
||||
this.port = 0;
|
||||
setRemote(remoteAddr, remotePort);
|
||||
|
||||
addChildService(server);
|
||||
addChildService(client);
|
||||
addChildService(recording);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean initialize() {
|
||||
getIntentManager().initialize();
|
||||
registerForIntent(ClientConnectionChangedIntent.class, ccci -> processClientStatusChanged(ccci));
|
||||
registerForIntent(ServerConnectionChangedIntent.class, scci -> processServerStatusChanged(scci));
|
||||
registerForIntent(ClientToServerPacketIntent.class, ctspi -> onDataClientToServer(ctspi.getData()));
|
||||
registerForIntent(ServerToClientPacketIntent.class, stcpi -> onDataServerToClient(stcpi.getRawData()));
|
||||
return super.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean terminate() {
|
||||
getIntentManager().terminate();
|
||||
return super.terminate();
|
||||
}
|
||||
|
||||
public void setCallback(ConnectionCallback callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
public boolean setRemote(InetAddress addr, int port) {
|
||||
this.addr = addr;
|
||||
this.port = port;
|
||||
recording.setAddress(new InetSocketAddress("::1", 0), server.getRemoteAddress());
|
||||
server.setRemoteAddress(addr, port);
|
||||
return true;
|
||||
}
|
||||
|
||||
public InetAddress getRemoteAddress() {
|
||||
return addr;
|
||||
}
|
||||
|
||||
public int getRemotePort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public int getLoginPort() {
|
||||
return client.getLoginPort();
|
||||
}
|
||||
|
||||
public int getZonePort() {
|
||||
return client.getZonePort();
|
||||
}
|
||||
|
||||
public long getServerToClientCount() {
|
||||
return serverToClient.get();
|
||||
}
|
||||
|
||||
public long getClientToServerCount() {
|
||||
return clientToServer.get();
|
||||
}
|
||||
|
||||
public InterceptorProperties getInterceptorProperties() {
|
||||
return client.getInterceptorProperties();
|
||||
}
|
||||
|
||||
private void processServerStatusChanged(ServerConnectionChangedIntent scci) {
|
||||
if (callback != null)
|
||||
callback.onServerStatusChanged(scci.getOldStatus(), scci.getStatus());
|
||||
if (scci.getStatus() == ServerConnectionStatus.DISCONNECTED && scci.getReason() != ServerConnectionChangedReason.CLIENT_DISCONNECT) {
|
||||
Log.i("Shutting down client due to server status: %s and reason %s", scci.getStatus(), scci.getReason());
|
||||
String title = "";
|
||||
String text = "";
|
||||
if (scci.getReason() != ServerConnectionChangedReason.INVALID_PROTOCOL) {
|
||||
title = "Connection Lost";
|
||||
text = "\n" + scci.getReason().name().replace('_', ' ');
|
||||
} else {
|
||||
title = "Network";
|
||||
text = "\nInvalid protocol version!";
|
||||
text += "\nTry updating your launcher to the latest version.";
|
||||
text += "\nInstalled Version: " + VERSION;
|
||||
}
|
||||
client.sendPackaged(new ErrorMessage(title, text, false));
|
||||
client.waitForClientAcknowledge();
|
||||
Delay.sleepMilli(100);
|
||||
client.disconnect(DisconnectReason.OTHER_SIDE_TERMINATED);
|
||||
}
|
||||
}
|
||||
|
||||
private void processClientStatusChanged(ClientConnectionChangedIntent ccci) {
|
||||
switch (ccci.getStatus()) {
|
||||
case LOGIN_CONNECTED:
|
||||
onClientConnected();
|
||||
break;
|
||||
case DISCONNECTED:
|
||||
onClientDisconnected();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void onClientConnected() {
|
||||
if (callback != null)
|
||||
callback.onClientConnected();
|
||||
}
|
||||
|
||||
private void onClientDisconnected() {
|
||||
if (callback != null)
|
||||
callback.onClientDisconnected();
|
||||
}
|
||||
|
||||
private void onDataServerToClient(byte [] data) {
|
||||
serverToClient.addAndGet(data.length);
|
||||
if (callback != null)
|
||||
callback.onDataServerToClient(data);
|
||||
}
|
||||
|
||||
private void onDataClientToServer(byte [] data) {
|
||||
clientToServer.addAndGet(data.length);
|
||||
if (callback != null)
|
||||
callback.onDataClientToServer(data);
|
||||
}
|
||||
|
||||
public interface ConnectionCallback {
|
||||
void onServerStatusChanged(ServerConnectionStatus oldStatus, ServerConnectionStatus status);
|
||||
void onClientConnected();
|
||||
void onClientDisconnected();
|
||||
void onDataServerToClient(byte [] data);
|
||||
void onDataClientToServer(byte [] data);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
package com.projectswg;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import com.projectswg.Connections.ConnectionCallback;
|
||||
import com.projectswg.common.debug.Log;
|
||||
import com.projectswg.common.debug.Log.LogLevel;
|
||||
import com.projectswg.common.debug.log_wrapper.ConsoleLogWrapper;
|
||||
import com.projectswg.connection.ServerConnectionStatus;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.PasswordField;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.ColumnConstraints;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
public class Forwarder extends Application implements ConnectionCallback {
|
||||
|
||||
private static final String [] DATA_NAMES = new String[]{"B", "KB", "MB", "GB", "TB"};
|
||||
|
||||
private final HolocoreConnection connections;
|
||||
private final ExecutorService executor;
|
||||
private final TextField usernameField;
|
||||
private final TextField passwordField;
|
||||
private final TextField serverIpField;
|
||||
private final TextField serverPortField;
|
||||
private final Text serverConnectionText;
|
||||
private final Text serverStatusText;
|
||||
private final Text clientConnectionText;
|
||||
private final Text clientConnectionPort;
|
||||
private final Text serverToClientText;
|
||||
private final Text clientToServerText;
|
||||
|
||||
public static void main(String [] args) {
|
||||
Log.addWrapper(new ConsoleLogWrapper(LogLevel.VERBOSE));
|
||||
launch(args);
|
||||
}
|
||||
|
||||
public Forwarder() {
|
||||
executor = Executors.newSingleThreadExecutor();
|
||||
usernameField = new TextField("");
|
||||
passwordField = new PasswordField();
|
||||
serverIpField = new TextField();
|
||||
serverPortField = new TextField();
|
||||
serverConnectionText = new Text(getConnectionStatus(false));
|
||||
serverStatusText = new Text(ServerConnectionStatus.DISCONNECTED.name());
|
||||
clientConnectionText = new Text(getConnectionStatus(false));
|
||||
clientConnectionPort = new Text();
|
||||
serverToClientText = new Text("0");
|
||||
clientToServerText = new Text("0");
|
||||
connections = new HolocoreConnection();
|
||||
updateConnection(serverConnectionText, false);
|
||||
updateConnection(clientConnectionText, false);
|
||||
usernameField.promptTextProperty().setValue("Username");
|
||||
passwordField.promptTextProperty().setValue("Password");
|
||||
serverIpField.setOnKeyPressed((event) -> updateServerIp());
|
||||
serverPortField.setOnKeyPressed((event) -> updateServerIp());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServerStatusChanged(ServerConnectionStatus oldStatus, ServerConnectionStatus status) {
|
||||
Platform.runLater(() -> {
|
||||
serverStatusText.setText(status.name().replace('_', ' '));
|
||||
updateConnection(serverConnectionText, status == ServerConnectionStatus.CONNECTED);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClientConnected() {
|
||||
Platform.runLater(() -> updateConnection(clientConnectionText, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClientDisconnected() {
|
||||
Platform.runLater(() -> updateConnection(clientConnectionText, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataClientToServer(byte[] data) {
|
||||
Platform.runLater(() -> clientToServerText.setText(getByteName(connections.getClientToServerCount())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataServerToClient(byte[] data) {
|
||||
Platform.runLater(() -> serverToClientText.setText(getByteName(connections.getServerToClientCount())));
|
||||
}
|
||||
|
||||
private void updateConnection(Text t, boolean status) {
|
||||
t.setText(getConnectionStatus(status));
|
||||
t.setFill(status ? Color.GREEN : Color.RED);
|
||||
}
|
||||
|
||||
private void updateServerIp() {
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
InetAddress addr = InetAddress.getByName(serverIpField.getText());
|
||||
int port = Integer.parseInt(serverPortField.getText());
|
||||
connections.setRemote(addr, port);
|
||||
} catch (UnknownHostException e) {
|
||||
Log.e("Unknown IP: " + serverIpField.getText());
|
||||
} catch (NumberFormatException e) {
|
||||
Log.e("Invalid Port: " + serverPortField.getText());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) throws Exception {
|
||||
initializeConnections();
|
||||
clientConnectionPort.setText(Integer.toString(connections.getLoginPort()));
|
||||
GridPane root = new GridPane();
|
||||
setupGridPane(root);
|
||||
Scene scene = new Scene(root, 400, 160);
|
||||
primaryStage.setTitle("Holocore Forwarder [" + Connections.VERSION + "]");
|
||||
primaryStage.setScene(scene);
|
||||
primaryStage.setMinWidth(scene.getWidth());
|
||||
primaryStage.setMinHeight(scene.getHeight());
|
||||
primaryStage.setMaxWidth(scene.getWidth());
|
||||
primaryStage.setMaxHeight(scene.getHeight());
|
||||
root.setOnMouseClicked((event) -> root.requestFocus());
|
||||
root.requestFocus();
|
||||
primaryStage.show();
|
||||
primaryStage.setOnCloseRequest(we -> {
|
||||
try {
|
||||
connections.stop();
|
||||
} catch (Exception e) {
|
||||
Log.e(e);
|
||||
}
|
||||
primaryStage.close();
|
||||
});
|
||||
}
|
||||
|
||||
private void initializeConnections() {
|
||||
connections.start();
|
||||
serverIpField.setText(connections.getRemoteAddress().getHostAddress());
|
||||
serverPortField.setText(Integer.toString(connections.getRemotePort()));
|
||||
clientConnectionPort.setText(Integer.toString(connections.getLoginPort()));
|
||||
usernameField.setText(connections.getInterceptorProperties().getUsername());
|
||||
passwordField.setText(connections.getInterceptorProperties().getPassword());
|
||||
usernameField.textProperty().addListener((event, oldValue, newValue) -> connections.getInterceptorProperties().setUsername(newValue));
|
||||
passwordField.textProperty().addListener((event, oldValue, newValue) -> connections.getInterceptorProperties().setPassword(newValue));
|
||||
connections.setCallback(this);
|
||||
}
|
||||
|
||||
private void setupGridPane(GridPane root) {
|
||||
addColumnConstraint(root, 50);
|
||||
addColumnConstraint(root, 100);
|
||||
addColumnConstraint(root, 75);
|
||||
addColumnConstraint(root, 175);
|
||||
root.add(usernameField, 0, 0, 2, 1);
|
||||
root.add(passwordField, 2, 0, 2, 1);
|
||||
root.add(serverIpField, 0, 1, 2, 1);
|
||||
root.add(serverPortField, 2, 1, 1, 1);
|
||||
root.add(new Text("Server Connection:"), 0, 2, 2, 1);
|
||||
root.add(serverConnectionText, 2, 2, 1, 1);
|
||||
root.add(serverStatusText, 3, 2, 1, 1);
|
||||
root.add(new Text("Client Connection:"), 0, 3, 2, 1);
|
||||
root.add(clientConnectionText, 2, 3, 2, 1);
|
||||
root.add(clientConnectionPort, 3, 3, 1, 1);
|
||||
root.add(new Text("Server->Client"), 0, 4, 2, 1);
|
||||
root.add(serverToClientText, 2, 4, 2, 1);
|
||||
root.add(new Text("Client->Server"), 0, 5, 2, 1);
|
||||
root.add(clientToServerText, 2, 5, 2, 1);
|
||||
}
|
||||
|
||||
private void addColumnConstraint(GridPane root, double width) {
|
||||
ColumnConstraints cc = new ColumnConstraints();
|
||||
cc.setPrefWidth(width);
|
||||
root.getColumnConstraints().add(cc);
|
||||
}
|
||||
|
||||
private static String getByteName(long bytes) {
|
||||
int index = 0;
|
||||
double reduced = bytes;
|
||||
while (reduced >= 1024 && index < DATA_NAMES.length) {
|
||||
reduced /= 1024;
|
||||
index++;
|
||||
}
|
||||
if (index == 0)
|
||||
return bytes + " B";
|
||||
return String.format("%.2f %s", reduced, DATA_NAMES[index]);
|
||||
}
|
||||
|
||||
private static String getConnectionStatus(boolean status) {
|
||||
return status ? "ONLINE" : "OFFLINE";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
package com.projectswg;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import com.projectswg.Connections.ConnectionCallback;
|
||||
import com.projectswg.common.concurrency.Delay;
|
||||
import com.projectswg.common.debug.Log;
|
||||
import com.projectswg.networking.NetInterceptor.InterceptorProperties;
|
||||
import com.projectswg.resources.HolocorePreferences;
|
||||
|
||||
public class HolocoreConnection {
|
||||
|
||||
private static final InetAddress DEFAULT_ADDRESS;
|
||||
|
||||
static {
|
||||
InetAddress addr;
|
||||
try {
|
||||
addr = InetAddress.getByName("::1");
|
||||
} catch (UnknownHostException e) {
|
||||
addr = InetAddress.getLoopbackAddress();
|
||||
}
|
||||
DEFAULT_ADDRESS = addr;
|
||||
}
|
||||
|
||||
private final AtomicBoolean running;
|
||||
private final InetAddress remoteAddr;
|
||||
private final int remotePort;
|
||||
private final boolean timeout;
|
||||
private int loginPort;
|
||||
private Connections connections;
|
||||
|
||||
public HolocoreConnection() {
|
||||
this(DEFAULT_ADDRESS, 44463, 44453, true);
|
||||
}
|
||||
|
||||
public HolocoreConnection(InetAddress remoteAddr, int remotePort, int loginPort, boolean timeout) {
|
||||
this.remoteAddr = remoteAddr;
|
||||
this.remotePort = remotePort;
|
||||
this.timeout = timeout;
|
||||
this.loginPort = loginPort;
|
||||
this.running = new AtomicBoolean(false);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (running.getAndSet(true)) {
|
||||
Log.e("Not starting, already started!");
|
||||
return;
|
||||
}
|
||||
boolean success = false;
|
||||
int attempts = 0;
|
||||
while (!success) {
|
||||
Log.i("Initializing connections... attempt %d", attempts++);
|
||||
connections = new Connections(remoteAddr, remotePort, loginPort, timeout);
|
||||
success = connections.initialize() && connections.start();
|
||||
if (!success) {
|
||||
Log.e("Failed to initialize");
|
||||
connections.stop();
|
||||
connections.terminate();
|
||||
if (Delay.sleepMilli(50)) {
|
||||
Log.e("Interrupted while connecting!");
|
||||
return;
|
||||
}
|
||||
loginPort++;
|
||||
}
|
||||
}
|
||||
Log.i("Connections initialized.");
|
||||
setProperties();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (!running.getAndSet(false)) {
|
||||
Log.e("Not stopping, already stopped!");
|
||||
return;
|
||||
}
|
||||
if (connections == null) {
|
||||
Log.e("Not stopping, connections is null!");
|
||||
return;
|
||||
}
|
||||
updateProperties();
|
||||
connections.stop();
|
||||
connections.terminate();
|
||||
connections = null;
|
||||
Log.i("Connections terminated.");
|
||||
}
|
||||
|
||||
public void setCallback(ConnectionCallback callback) {
|
||||
connections.setCallback(callback);
|
||||
}
|
||||
|
||||
public boolean setRemote(InetAddress addr, int port) {
|
||||
return connections.setRemote(addr, port);
|
||||
}
|
||||
|
||||
public InetAddress getRemoteAddress() {
|
||||
return connections.getRemoteAddress();
|
||||
}
|
||||
|
||||
public int getRemotePort() {
|
||||
return connections.getRemotePort();
|
||||
}
|
||||
|
||||
public int getLoginPort() {
|
||||
return connections.getLoginPort();
|
||||
}
|
||||
|
||||
public int getZonePort() {
|
||||
return connections.getZonePort();
|
||||
}
|
||||
|
||||
public long getServerToClientCount() {
|
||||
return connections.getServerToClientCount();
|
||||
}
|
||||
|
||||
public long getClientToServerCount() {
|
||||
return connections.getClientToServerCount();
|
||||
}
|
||||
|
||||
public InterceptorProperties getInterceptorProperties() {
|
||||
return connections.getInterceptorProperties();
|
||||
}
|
||||
|
||||
private void setProperties() {
|
||||
HolocorePreferences pref = HolocorePreferences.getInstance();
|
||||
InterceptorProperties inter = getInterceptorProperties();
|
||||
inter.setUsername(pref.getUsername());
|
||||
inter.setPassword(pref.getPassword());
|
||||
}
|
||||
|
||||
private void updateProperties() {
|
||||
HolocorePreferences pref = HolocorePreferences.getInstance();
|
||||
InterceptorProperties inter = getInterceptorProperties();
|
||||
if (!inter.getUsername().isEmpty())
|
||||
pref.setUsername(inter.getUsername());
|
||||
if (!inter.getPassword().isEmpty())
|
||||
pref.setPassword(inter.getPassword());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package com.projectswg.intents;
|
||||
|
||||
import com.projectswg.common.control.Intent;
|
||||
import com.projectswg.resources.ClientConnectionStatus;
|
||||
|
||||
public class ClientConnectionChangedIntent extends Intent {
|
||||
|
||||
private ClientConnectionStatus old;
|
||||
private ClientConnectionStatus status;
|
||||
|
||||
public ClientConnectionChangedIntent(ClientConnectionStatus old, ClientConnectionStatus status) {
|
||||
setOldStatus(old);
|
||||
setStatus(status);
|
||||
}
|
||||
|
||||
public ClientConnectionStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(ClientConnectionStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public ClientConnectionStatus getOldStatus() {
|
||||
return old;
|
||||
}
|
||||
|
||||
public void setOldStatus(ClientConnectionStatus old) {
|
||||
this.old = old;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package com.projectswg.intents;
|
||||
|
||||
import com.projectswg.common.control.Intent;
|
||||
import com.projectswg.networking.Packet;
|
||||
|
||||
public class ClientSonyPacketIntent extends Intent {
|
||||
|
||||
private Packet packet;
|
||||
|
||||
public ClientSonyPacketIntent() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public ClientSonyPacketIntent(Packet packet) {
|
||||
setPacket(packet);
|
||||
}
|
||||
|
||||
public Packet getPacket() {
|
||||
return packet;
|
||||
}
|
||||
|
||||
public void setPacket(Packet packet) {
|
||||
this.packet = packet;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.projectswg.intents;
|
||||
|
||||
import com.projectswg.common.control.Intent;
|
||||
|
||||
public class ClientToServerPacketIntent extends Intent {
|
||||
|
||||
private byte [] data;
|
||||
|
||||
public ClientToServerPacketIntent(byte [] data) {
|
||||
setData(data);
|
||||
}
|
||||
|
||||
public byte [] getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(byte [] data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package com.projectswg.intents;
|
||||
|
||||
import com.projectswg.common.control.Intent;
|
||||
import com.projectswg.connection.ServerConnectionChangedReason;
|
||||
import com.projectswg.connection.ServerConnectionStatus;
|
||||
|
||||
public class ServerConnectionChangedIntent extends Intent {
|
||||
|
||||
private ServerConnectionStatus old;
|
||||
private ServerConnectionStatus status;
|
||||
private ServerConnectionChangedReason reason;
|
||||
|
||||
public ServerConnectionChangedIntent(ServerConnectionStatus old, ServerConnectionStatus status, ServerConnectionChangedReason reason) {
|
||||
setOldStatus(old);
|
||||
setStatus(status);
|
||||
setReason(reason);
|
||||
}
|
||||
|
||||
public ServerConnectionStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public ServerConnectionStatus getOldStatus() {
|
||||
return old;
|
||||
}
|
||||
|
||||
public ServerConnectionChangedReason getReason() {
|
||||
return reason;
|
||||
}
|
||||
|
||||
public void setStatus(ServerConnectionStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public void setOldStatus(ServerConnectionStatus old) {
|
||||
this.old = old;
|
||||
}
|
||||
|
||||
public void setReason(ServerConnectionChangedReason reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package com.projectswg.intents;
|
||||
|
||||
import com.projectswg.common.control.Intent;
|
||||
|
||||
public class ServerToClientPacketIntent extends Intent {
|
||||
|
||||
private int crc;
|
||||
private byte [] rawData;
|
||||
|
||||
public ServerToClientPacketIntent(int crc, byte [] rawData) {
|
||||
setCrc(crc);
|
||||
setRawData(rawData);
|
||||
}
|
||||
|
||||
public int getCrc() {
|
||||
return crc;
|
||||
}
|
||||
|
||||
public byte [] getRawData() {
|
||||
return rawData;
|
||||
}
|
||||
|
||||
public void setCrc(int crc) {
|
||||
this.crc = crc;
|
||||
}
|
||||
|
||||
public void setRawData(byte [] rawData) {
|
||||
this.rawData = rawData;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
package com.projectswg.networking;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import com.projectswg.common.data.encodables.galaxy.Galaxy;
|
||||
import com.projectswg.common.network.NetBuffer;
|
||||
import com.projectswg.common.network.packets.PacketType;
|
||||
import com.projectswg.common.network.packets.swg.login.LoginClientId;
|
||||
import com.projectswg.common.network.packets.swg.login.LoginClusterStatus;
|
||||
import com.projectswg.networking.client.ClientData;
|
||||
|
||||
public class NetInterceptor {
|
||||
|
||||
private final InterceptorProperties properties;
|
||||
private final ClientData clientData;
|
||||
|
||||
public NetInterceptor(ClientData clientData) {
|
||||
this.properties = new InterceptorProperties();
|
||||
this.clientData = clientData;
|
||||
}
|
||||
|
||||
public InterceptorProperties getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
public byte [] interceptClient(byte [] data) {
|
||||
if (data.length < 6)
|
||||
return data;
|
||||
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
|
||||
PacketType type = PacketType.fromCrc(bb.getInt(2));
|
||||
switch (type) {
|
||||
case LOGIN_CLIENT_ID:
|
||||
return setAutoLogin(NetBuffer.wrap(bb));
|
||||
case CMD_SCENE_READY:
|
||||
clientData.setZoning(false);
|
||||
return data;
|
||||
default:
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
public byte [] interceptServer(byte [] data) {
|
||||
if (data.length < 6)
|
||||
return data;
|
||||
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
|
||||
PacketType type = PacketType.fromCrc(bb.getInt(2));
|
||||
switch (type) {
|
||||
case LOGIN_CLUSTER_STATUS:
|
||||
return getServerList(NetBuffer.wrap(bb));
|
||||
default:
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
private byte [] setAutoLogin(NetBuffer data) {
|
||||
LoginClientId id = new LoginClientId(data);
|
||||
if (!id.getUsername().equals(properties.getUsername()) || !id.getPassword().isEmpty())
|
||||
return data.array();
|
||||
id.setPassword(properties.getPassword());
|
||||
return id.encode().array();
|
||||
}
|
||||
|
||||
private byte [] getServerList(NetBuffer data) {
|
||||
LoginClusterStatus cluster = new LoginClusterStatus();
|
||||
cluster.decode(data);
|
||||
for (Galaxy g : cluster.getGalaxies()) {
|
||||
g.setAddress("127.0.0.1");
|
||||
g.setZonePort((short) properties.getPort());
|
||||
g.setPingPort((short) properties.getPort());
|
||||
}
|
||||
return cluster.encode().array();
|
||||
}
|
||||
|
||||
public static class InterceptorProperties {
|
||||
|
||||
private int port;
|
||||
private String username;
|
||||
private String password;
|
||||
|
||||
public InterceptorProperties() {
|
||||
port = 0;
|
||||
username = "";
|
||||
password = "";
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
package com.projectswg.networking.client;
|
||||
|
||||
import com.projectswg.common.concurrency.Delay;
|
||||
import com.projectswg.common.concurrency.PswgBasicScheduledThread;
|
||||
import com.projectswg.common.control.Manager;
|
||||
import com.projectswg.common.debug.Log;
|
||||
import com.projectswg.common.network.packets.SWGPacket;
|
||||
import com.projectswg.common.network.packets.swg.zone.HeartBeat;
|
||||
import com.projectswg.intents.ServerToClientPacketIntent;
|
||||
import com.projectswg.networking.NetInterceptor;
|
||||
import com.projectswg.networking.NetInterceptor.InterceptorProperties;
|
||||
import com.projectswg.networking.Packet;
|
||||
import com.projectswg.networking.client.ClientServerSocket.IncomingPacket;
|
||||
import com.projectswg.networking.client.receiver.ClientInboundService;
|
||||
import com.projectswg.networking.client.sender.ClientOutboundService;
|
||||
import com.projectswg.networking.soe.Disconnect;
|
||||
import com.projectswg.networking.soe.Disconnect.DisconnectReason;
|
||||
import com.projectswg.resources.ClientConnectionStatus;
|
||||
|
||||
public class ClientConnectionService extends Manager implements ClientPacketSender {
|
||||
|
||||
private final NetInterceptor interceptor;
|
||||
private final ClientData clientData;
|
||||
/* Send-related variables */
|
||||
private final ClientOutboundService outboundService;
|
||||
private final ClientServerSocket server;
|
||||
/* Receive-related variables */
|
||||
private final PswgBasicScheduledThread pingerThread;
|
||||
private final ClientInboundService inboundService;
|
||||
|
||||
public ClientConnectionService(int initialLoginPort, boolean timeout) {
|
||||
this.clientData = new ClientData();
|
||||
this.interceptor = new NetInterceptor(clientData);
|
||||
this.server = new ClientServerSocket(clientData, initialLoginPort);
|
||||
this.outboundService = new ClientOutboundService(clientData, interceptor, this);
|
||||
this.inboundService = new ClientInboundService(interceptor, clientData, this);
|
||||
this.pingerThread = new PswgBasicScheduledThread("client-pinger", () -> ping(timeout));
|
||||
|
||||
addChildService(outboundService);
|
||||
addChildService(inboundService);
|
||||
}
|
||||
|
||||
public InterceptorProperties getInterceptorProperties() {
|
||||
return interceptor.getProperties();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean initialize() {
|
||||
registerForIntent(ServerToClientPacketIntent.class, stcpi -> handleServerToClientPacketIntent(stcpi));
|
||||
return super.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean start() {
|
||||
if (!server.connect(incoming -> onPacket(incoming)))
|
||||
return false;
|
||||
interceptor.getProperties().setPort(server.getZonePort());
|
||||
pingerThread.startWithFixedRate(0, 1000);
|
||||
return super.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stop() {
|
||||
pingerThread.stop();
|
||||
pingerThread.awaitTermination(3000);
|
||||
server.disconnect();
|
||||
return super.stop();
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return server.isConnected();
|
||||
}
|
||||
|
||||
public void waitForClientAcknowledge() {
|
||||
while (clientData.isWaitingForClientAcknowledge()) {
|
||||
if (Delay.sleepMicro(5))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void disconnect(DisconnectReason reason) {
|
||||
if (clientData.getConnectionId() != -1)
|
||||
sendRaw(new Disconnect(clientData.getConnectionId(), reason));
|
||||
inboundService.disconnect();
|
||||
}
|
||||
|
||||
public int getLoginPort() {
|
||||
return server.getLoginPort();
|
||||
}
|
||||
|
||||
public int getZonePort() {
|
||||
return server.getZonePort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPackaged(byte[] ... packets) {
|
||||
if (!clientData.isConnectionInitialized())
|
||||
return;
|
||||
for (byte[] data : packets)
|
||||
outboundService.sendSequential(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPackaged(SWGPacket ... packets) {
|
||||
if (!clientData.isConnectionInitialized())
|
||||
return;
|
||||
for (SWGPacket p : packets)
|
||||
outboundService.sendSequential(p.encode().array());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendRaw(byte[]... packets) {
|
||||
if (!clientData.isConnectionInitialized())
|
||||
return;
|
||||
for (byte [] data : packets)
|
||||
server.send(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendRaw(Packet... packets) {
|
||||
if (!clientData.isConnectionInitialized())
|
||||
return;
|
||||
for (Packet p : packets)
|
||||
server.send(p.encode().array());
|
||||
}
|
||||
|
||||
private void handleServerToClientPacketIntent(ServerToClientPacketIntent stcpi) {
|
||||
sendPackaged(stcpi.getRawData());
|
||||
}
|
||||
|
||||
private void onPacket(IncomingPacket incoming) {
|
||||
if (incoming.getLength() == 4 && incoming.getData()[0] != 0) { // Reasonable to assume this is a ping packet
|
||||
server.send(incoming.getPacket());
|
||||
return;
|
||||
}
|
||||
inboundService.addPacket(incoming);
|
||||
}
|
||||
|
||||
private void ping(boolean timeout) {
|
||||
if (clientData.getStatus() == ClientConnectionStatus.DISCONNECTED || !timeout)
|
||||
return;
|
||||
if (clientData.isTimedOut()) {
|
||||
Log.i("Disconnecting due to timeout! Time Since Packet: %.2fms Zoning: %b", clientData.getTimeSinceLastAcknowledgement(), clientData.isZoning());
|
||||
disconnect(DisconnectReason.TIMEOUT);
|
||||
} else {
|
||||
sendPackaged(new HeartBeat().encode().array());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
package com.projectswg.networking.client;
|
||||
|
||||
import com.projectswg.networking.client.ClientServerSocket.ClientServer;
|
||||
import com.projectswg.resources.ClientConnectionStatus;
|
||||
|
||||
public class ClientData {
|
||||
|
||||
private int connectionId;
|
||||
private int communicationPort;
|
||||
private short rxSequence;
|
||||
private short txSequence;
|
||||
private short ackSequence;
|
||||
private short oooSequence;
|
||||
private long lastAcknowledgement;
|
||||
private boolean zoning;
|
||||
private ClientServer server;
|
||||
private ClientConnectionStatus status;
|
||||
|
||||
public ClientData() {
|
||||
reset(ClientConnectionStatus.DISCONNECTED);
|
||||
}
|
||||
|
||||
public void resetConnectionInfo() {
|
||||
setConnectionId(-1);
|
||||
setCommunicationPort(0);
|
||||
setRxSequence((short) -1);
|
||||
setTxSequence((short) 0);
|
||||
setAckSequence((short) 0);
|
||||
setOOOSequence((short) 0);
|
||||
setLastAcknowledgement(0);
|
||||
setZoning(false);
|
||||
}
|
||||
|
||||
public void reset(ClientConnectionStatus status) {
|
||||
resetConnectionInfo();
|
||||
setStatus(status);
|
||||
if (status == ClientConnectionStatus.LOGIN_CONNECTED)
|
||||
setClientServer(ClientServer.LOGIN);
|
||||
else if (status == ClientConnectionStatus.ZONE_CONNECTED)
|
||||
setClientServer(ClientServer.ZONE);
|
||||
else
|
||||
setClientServer(ClientServer.NONE);
|
||||
}
|
||||
|
||||
public int getConnectionId() {
|
||||
return connectionId;
|
||||
}
|
||||
|
||||
public int getCommunicationPort() {
|
||||
return communicationPort;
|
||||
}
|
||||
|
||||
public short getRxSequence() {
|
||||
return rxSequence;
|
||||
}
|
||||
|
||||
public short getTxSequence() {
|
||||
return txSequence;
|
||||
}
|
||||
|
||||
public short getAckSequence() {
|
||||
return ackSequence;
|
||||
}
|
||||
|
||||
public short getOOOSequence() {
|
||||
return oooSequence;
|
||||
}
|
||||
|
||||
public long getLastAcknowledgement() {
|
||||
return lastAcknowledgement;
|
||||
}
|
||||
|
||||
public boolean isZoning() {
|
||||
return zoning;
|
||||
}
|
||||
|
||||
public double getTimeSinceLastAcknowledgement() {
|
||||
long last = lastAcknowledgement;
|
||||
if (last == 0)
|
||||
return 0;
|
||||
return (System.nanoTime() - last) / 1E6;
|
||||
}
|
||||
|
||||
public boolean isTimedOut() {
|
||||
if (getTimeSinceLastAcknowledgement() > 5000 && !zoning)
|
||||
return true;
|
||||
return getTimeSinceLastAcknowledgement() > 30000 && zoning;
|
||||
}
|
||||
|
||||
public ClientServer getClientServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
public ClientConnectionStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public boolean isWaitingForClientAcknowledge() {
|
||||
return txSequence-1 > ackSequence && connectionId != -1;
|
||||
}
|
||||
|
||||
public boolean isConnectionInitialized() {
|
||||
return connectionId != -1 && communicationPort > 0;
|
||||
}
|
||||
|
||||
public void setConnectionId(int connectionId) {
|
||||
this.connectionId = connectionId;
|
||||
}
|
||||
|
||||
public void setCommunicationPort(int communicationPort) {
|
||||
this.communicationPort = communicationPort;
|
||||
}
|
||||
|
||||
public void setRxSequence(short rxSequence) {
|
||||
this.rxSequence = rxSequence;
|
||||
}
|
||||
|
||||
public void setTxSequence(short txSequence) {
|
||||
this.txSequence = txSequence;
|
||||
}
|
||||
|
||||
public void setAckSequence(short ackSequence) {
|
||||
this.ackSequence = ackSequence;
|
||||
}
|
||||
|
||||
public void setOOOSequence(short oooSequence) {
|
||||
this.oooSequence = oooSequence;
|
||||
}
|
||||
|
||||
public void setLastAcknowledgement(long lastAcknowledgement) {
|
||||
this.lastAcknowledgement = lastAcknowledgement;
|
||||
}
|
||||
|
||||
public void setZoning(boolean zoning) {
|
||||
this.zoning = zoning;
|
||||
}
|
||||
|
||||
public void setClientServer(ClientServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public ClientConnectionStatus setStatus(ClientConnectionStatus status) {
|
||||
ClientConnectionStatus old = this.status;
|
||||
this.status = status;
|
||||
return old;
|
||||
}
|
||||
|
||||
public short getAndIncrementRxSequence() {
|
||||
return rxSequence++;
|
||||
}
|
||||
|
||||
public short getAndIncrementTxSequence() {
|
||||
return txSequence++;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.projectswg.networking.client;
|
||||
|
||||
import com.projectswg.common.network.packets.SWGPacket;
|
||||
import com.projectswg.networking.Packet;
|
||||
|
||||
public interface ClientPacketSender {
|
||||
|
||||
/** Adds the specified packets to a buffer to guarantee sending in-order */
|
||||
void sendPackaged(byte[] ... packets);
|
||||
/** Adds the specified packets to a buffer to guarantee sending in-order */
|
||||
void sendPackaged(SWGPacket ... packets);
|
||||
/** Sends the specified packets via UDP immediately */
|
||||
void sendRaw(byte[] ... data);
|
||||
/** Sends the specified packets via UDP immediately */
|
||||
void sendRaw(Packet ... p);
|
||||
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
package com.projectswg.networking.client;
|
||||
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import com.projectswg.common.debug.Assert;
|
||||
import com.projectswg.common.debug.Log;
|
||||
import com.projectswg.networking.UDPServer;
|
||||
|
||||
/**
|
||||
* This class is in charge of the login and zone UDP servers, and switching between the two
|
||||
*/
|
||||
public class ClientServerSocket {
|
||||
|
||||
private static final InetAddress ADDR = InetAddress.getLoopbackAddress();
|
||||
|
||||
private final AtomicBoolean connected;
|
||||
private final ClientData data;
|
||||
private UDPServer loginServer;
|
||||
private UDPServer zoneServer;
|
||||
private int loginPort;
|
||||
|
||||
public ClientServerSocket(ClientData data, int loginPort) {
|
||||
Assert.test(loginPort >= 0, "Login port must be positive!");
|
||||
this.connected = new AtomicBoolean(false);
|
||||
this.data = data;
|
||||
this.loginServer = null;
|
||||
this.zoneServer = null;
|
||||
setLoginPort(loginPort);
|
||||
}
|
||||
|
||||
public boolean connect(SocketCallback callback) {
|
||||
Assert.test(!connected.getAndSet(true));
|
||||
Assert.notNull(callback);
|
||||
try {
|
||||
InetAddress loopback = InetAddress.getLoopbackAddress();
|
||||
loginServer = new UDPServer(new InetSocketAddress(loopback, loginPort), 496);
|
||||
zoneServer = new UDPServer(new InetSocketAddress(loopback, 0), 496);
|
||||
loginServer.bind();
|
||||
zoneServer.bind();
|
||||
loginPort = loginServer.getPort();
|
||||
Assert.test(loginPort > 0, "Login port was not set correctly by the UDPServer!");
|
||||
loginServer.setCallback((packet) -> callback.onPacket(new IncomingPacket(ClientServer.LOGIN, packet)));
|
||||
zoneServer.setCallback((packet) -> callback.onPacket(new IncomingPacket(ClientServer.ZONE, packet)));
|
||||
return true;
|
||||
} catch (SocketException e) {
|
||||
disconnect();
|
||||
Log.e(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void disconnect() {
|
||||
Assert.test(connected.getAndSet(false));
|
||||
Assert.notNull(loginServer);
|
||||
Assert.notNull(zoneServer);
|
||||
loginServer.removeCallback();
|
||||
loginServer.close();
|
||||
zoneServer.removeCallback();
|
||||
zoneServer.close();
|
||||
}
|
||||
|
||||
public void send(byte [] packet) {
|
||||
if (data.getClientServer() == ClientServer.NONE)
|
||||
return;
|
||||
Assert.test(packet.length > 0, "Packet length cannot be 0!");
|
||||
Assert.test(data.getCommunicationPort() > 0, "Communication port has not been set!");
|
||||
getServer().send(data.getCommunicationPort(), ADDR, packet);
|
||||
}
|
||||
|
||||
public void send(DatagramPacket packet) {
|
||||
if (data.getClientServer() == ClientServer.NONE)
|
||||
return;
|
||||
Assert.test(packet.getLength() > 0, "Packet length cannot be 0!");
|
||||
Assert.test(packet.getData().length == packet.getLength(), "Data length and packet length do not match!");
|
||||
getServer().send(packet);
|
||||
}
|
||||
|
||||
private UDPServer getServer() {
|
||||
switch (data.getClientServer()) {
|
||||
case LOGIN:
|
||||
return loginServer;
|
||||
case ZONE:
|
||||
return zoneServer;
|
||||
default:
|
||||
Assert.fail("Unknown server: " + data.getClientServer());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setLoginPort(int loginPort) {
|
||||
Assert.test(loginPort >= 0, "Login port must be >= 0");
|
||||
this.loginPort = loginPort;
|
||||
}
|
||||
|
||||
public int getLoginPort() {
|
||||
Assert.notNull(loginServer, "Login server has not been initialized");
|
||||
return loginServer.getPort();
|
||||
}
|
||||
|
||||
public int getZonePort() {
|
||||
Assert.notNull(zoneServer, "Zone server has not been initialized");
|
||||
return zoneServer.getPort();
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return connected.get();
|
||||
}
|
||||
|
||||
public enum ClientServer {
|
||||
NONE,
|
||||
LOGIN,
|
||||
ZONE
|
||||
}
|
||||
|
||||
public interface SocketCallback {
|
||||
void onPacket(IncomingPacket incoming);
|
||||
}
|
||||
|
||||
public static class IncomingPacket {
|
||||
private final ClientServer server;
|
||||
private final DatagramPacket packet;
|
||||
|
||||
public IncomingPacket(ClientServer server, DatagramPacket packet) {
|
||||
Assert.notNull(server, "Server cannot be null");
|
||||
Assert.notNull(packet, "Packet cannot be null");
|
||||
this.server = server;
|
||||
this.packet = packet;
|
||||
}
|
||||
|
||||
public ClientServer getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
public DatagramPacket getPacket() {
|
||||
return packet;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return packet.getPort();
|
||||
}
|
||||
|
||||
public byte [] getData() {
|
||||
return packet.getData();
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return packet.getData().length;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,277 +0,0 @@
|
||||
package com.projectswg.networking.client.receiver;
|
||||
|
||||
import java.nio.BufferUnderflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import com.projectswg.common.concurrency.PswgTaskThreadPool;
|
||||
import com.projectswg.common.control.IntentChain;
|
||||
import com.projectswg.common.control.IntentManager;
|
||||
import com.projectswg.common.debug.Log;
|
||||
import com.projectswg.intents.ClientConnectionChangedIntent;
|
||||
import com.projectswg.intents.ClientSonyPacketIntent;
|
||||
import com.projectswg.intents.ClientToServerPacketIntent;
|
||||
import com.projectswg.networking.NetInterceptor;
|
||||
import com.projectswg.networking.Packet;
|
||||
import com.projectswg.networking.client.ClientData;
|
||||
import com.projectswg.networking.client.ClientPacketSender;
|
||||
import com.projectswg.networking.client.ClientServerSocket.ClientServer;
|
||||
import com.projectswg.networking.client.ClientServerSocket.IncomingPacket;
|
||||
import com.projectswg.networking.soe.Acknowledge;
|
||||
import com.projectswg.networking.soe.ClientNetworkStatusUpdate;
|
||||
import com.projectswg.networking.soe.DataChannel;
|
||||
import com.projectswg.networking.soe.Disconnect;
|
||||
import com.projectswg.networking.soe.Disconnect.DisconnectReason;
|
||||
import com.projectswg.networking.soe.Fragmented;
|
||||
import com.projectswg.networking.soe.KeepAlive;
|
||||
import com.projectswg.networking.soe.MultiPacket;
|
||||
import com.projectswg.networking.soe.OutOfOrder;
|
||||
import com.projectswg.networking.soe.SequencedPacket;
|
||||
import com.projectswg.networking.soe.ServerNetworkStatusUpdate;
|
||||
import com.projectswg.networking.soe.SessionRequest;
|
||||
import com.projectswg.networking.soe.SessionResponse;
|
||||
import com.projectswg.resources.ClientConnectionStatus;
|
||||
import com.projectswg.utilities.ByteUtilities;
|
||||
|
||||
public class ClientInboundProcessor {
|
||||
|
||||
private static final int MAX_PACKET_SIZE = 496;
|
||||
private static final int GALACTIC_BASE_TIME = 1323043200;
|
||||
|
||||
private final NetInterceptor interceptor;
|
||||
private final ClientData clientData;
|
||||
private final ClientFragmentedProcessor fragmentedProcessor;
|
||||
private final PswgTaskThreadPool<IncomingPacket> processorThreadPool;
|
||||
private final ClientPacketSender packetSender;
|
||||
|
||||
private IntentChain processorIntentChain;
|
||||
|
||||
public ClientInboundProcessor(NetInterceptor interceptor, ClientData data, ClientPacketSender packetSender) {
|
||||
this.interceptor = interceptor;
|
||||
this.clientData = data;
|
||||
this.fragmentedProcessor = new ClientFragmentedProcessor();
|
||||
this.processorThreadPool = new PswgTaskThreadPool<>(1, "packet-processor", packet -> process(packet, packet.getData()));
|
||||
this.packetSender = packetSender;
|
||||
this.processorIntentChain = null;
|
||||
}
|
||||
|
||||
public void start(IntentManager intentManager) {
|
||||
processorIntentChain = new IntentChain(intentManager);
|
||||
processorThreadPool.start();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
processorThreadPool.stop(false);
|
||||
processorThreadPool.awaitTermination(1000);
|
||||
}
|
||||
|
||||
public void addPacket(IncomingPacket packet) {
|
||||
processorThreadPool.addTask(packet);
|
||||
}
|
||||
|
||||
public void disconnect() {
|
||||
setConnectionState(ClientConnectionStatus.DISCONNECTED);
|
||||
}
|
||||
|
||||
public void onConnected() {
|
||||
fragmentedProcessor.reset();
|
||||
}
|
||||
|
||||
public void onDisconnected() {
|
||||
setConnectionState(ClientConnectionStatus.DISCONNECTED);
|
||||
clientData.reset(ClientConnectionStatus.DISCONNECTED);
|
||||
fragmentedProcessor.reset();
|
||||
}
|
||||
|
||||
private void process(IncomingPacket packet, byte [] raw) {
|
||||
if (raw.length < 2)
|
||||
return;
|
||||
ByteBuffer data = ByteBuffer.wrap(raw).order(ByteOrder.BIG_ENDIAN);
|
||||
if (data.get(0) != 0) {
|
||||
onSWGPacket(data.array());
|
||||
return;
|
||||
}
|
||||
Packet p;
|
||||
try {
|
||||
p = createPacket(data);
|
||||
if (p == null)
|
||||
return;
|
||||
} catch (BufferUnderflowException e) {
|
||||
Log.w("Invalid packet structure received! %s", ByteUtilities.getHexString(raw));
|
||||
return;
|
||||
}
|
||||
if (!(p instanceof SessionRequest) && clientData.getStatus() == ClientConnectionStatus.DISCONNECTED) {
|
||||
Log.i("Packet sent out of connection: %s", p);
|
||||
return;
|
||||
}
|
||||
handlePacket(packet, p);
|
||||
}
|
||||
|
||||
private Packet createPacket(ByteBuffer data) {
|
||||
short opcode = data.getShort(0);
|
||||
switch (opcode) {
|
||||
case 0x01: return new SessionRequest(data);
|
||||
case 0x03: return new MultiPacket(data);
|
||||
case 0x05: return new Disconnect(data);
|
||||
case 0x06: return new KeepAlive(data);
|
||||
case 0x07: return new ClientNetworkStatusUpdate(data);
|
||||
case 0x09:
|
||||
case 0x0A:
|
||||
case 0x0B:
|
||||
case 0x0C: return new DataChannel(data);
|
||||
case 0x0D:
|
||||
case 0x0E:
|
||||
case 0x0F:
|
||||
case 0x10: return new Fragmented(data);
|
||||
case 0x11:
|
||||
case 0x12:
|
||||
case 0x13:
|
||||
case 0x14: return new OutOfOrder(data);
|
||||
case 0x15:
|
||||
case 0x16:
|
||||
case 0x17:
|
||||
case 0x18: return new Acknowledge(data);
|
||||
default:
|
||||
Log.e("Unknown SOE packet: %d %s", opcode, ByteUtilities.getHexString(data.array()));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void handlePacket(IncomingPacket incoming, Packet p) {
|
||||
if (p instanceof SessionRequest) {
|
||||
onSessionRequest(incoming, (SessionRequest) p);
|
||||
processorIntentChain.broadcastAfter(new ClientSonyPacketIntent(p));
|
||||
return;
|
||||
}
|
||||
if (incoming.getServer() != clientData.getClientServer()) {
|
||||
Log.w("Dropping packet %s with invalid server %s [expected %s]", p, incoming.getServer(), clientData.getClientServer());
|
||||
return;
|
||||
}
|
||||
if (p instanceof MultiPacket)
|
||||
onMultiPacket(incoming, (MultiPacket) p);
|
||||
else if (p instanceof Disconnect)
|
||||
onDisconnect(incoming, (Disconnect) p);
|
||||
else if (p instanceof KeepAlive)
|
||||
onKeepAlive((KeepAlive) p);
|
||||
else if (p instanceof ClientNetworkStatusUpdate)
|
||||
onClientNetwork((ClientNetworkStatusUpdate) p);
|
||||
else if (p instanceof DataChannel)
|
||||
onDataChannel((DataChannel) p);
|
||||
else if (p instanceof Fragmented)
|
||||
onFragmented(incoming, (Fragmented) p);
|
||||
else if (p instanceof Acknowledge)
|
||||
onAcknowledge((Acknowledge) p);
|
||||
else if (p instanceof OutOfOrder)
|
||||
onOutOfOrder((OutOfOrder) p);
|
||||
else
|
||||
Log.e("Unhandled SOE packet: %s", p);
|
||||
processorIntentChain.broadcastAfter(new ClientSonyPacketIntent(p));
|
||||
}
|
||||
|
||||
private void onSessionRequest(IncomingPacket incoming, SessionRequest request) {
|
||||
ClientConnectionStatus newStatus;
|
||||
switch (incoming.getServer()) {
|
||||
case LOGIN:
|
||||
Log.i("Login Session Request [port set to %d]", incoming.getPort());
|
||||
newStatus = ClientConnectionStatus.LOGIN_CONNECTED;
|
||||
break;
|
||||
case ZONE:
|
||||
Log.i("Zone Session Request [switching port from %d to %d]", clientData.getCommunicationPort(), incoming.getPort());
|
||||
newStatus = ClientConnectionStatus.ZONE_CONNECTED;
|
||||
break;
|
||||
default:
|
||||
Log.i("Unknown server in session request! Server: %s", incoming.getServer());
|
||||
return;
|
||||
}
|
||||
clientData.resetConnectionInfo();
|
||||
clientData.setClientServer(incoming.getServer());
|
||||
if (incoming.getServer() == ClientServer.ZONE)
|
||||
packetSender.sendRaw(new Disconnect(clientData.getConnectionId(), DisconnectReason.NEW_CONNECTION_ATTEMPT));
|
||||
clientData.setConnectionId(request.getConnectionId());
|
||||
clientData.setCommunicationPort(incoming.getPort());
|
||||
packetSender.sendRaw(new SessionResponse(request.getConnectionId(), 0, (byte) 0, (byte) 0, (byte) 0, MAX_PACKET_SIZE));
|
||||
setConnectionState(newStatus);
|
||||
}
|
||||
|
||||
private void onMultiPacket(IncomingPacket incoming, MultiPacket packet) {
|
||||
for (byte [] p : packet.getPackets()) {
|
||||
process(incoming, p);
|
||||
}
|
||||
}
|
||||
|
||||
private void onDisconnect(IncomingPacket incoming, Disconnect disconnect) {
|
||||
if (disconnect.getConnectionId() != clientData.getConnectionId()) {
|
||||
Log.w("Ignoring old disconnect! Current ID: %d Disconnect ID: %d", clientData.getConnectionId(), disconnect.getConnectionId());
|
||||
return;
|
||||
}
|
||||
Log.i("Received client disconnect [port=%d reason=%s]", incoming.getPort(), disconnect.getReason());
|
||||
setConnectionState(ClientConnectionStatus.DISCONNECTED);
|
||||
}
|
||||
|
||||
private void onKeepAlive(KeepAlive alive) {
|
||||
packetSender.sendRaw(new KeepAlive());
|
||||
}
|
||||
|
||||
private void onClientNetwork(ClientNetworkStatusUpdate update) {
|
||||
ServerNetworkStatusUpdate serverNet = new ServerNetworkStatusUpdate();
|
||||
serverNet.setClientTickCount((short) update.getTick());
|
||||
serverNet.setServerSyncStampLong((int) (System.currentTimeMillis()-GALACTIC_BASE_TIME));
|
||||
serverNet.setClientPacketsSent(update.getSent());
|
||||
serverNet.setClientPacketsRecv(update.getRecv());
|
||||
serverNet.setServerPacketsSent(clientData.getTxSequence());
|
||||
serverNet.setServerPacketsRecv(clientData.getRxSequence()+1);
|
||||
packetSender.sendRaw(serverNet);
|
||||
}
|
||||
|
||||
private boolean validateSequenced(SequencedPacket sequenced) {
|
||||
short rx = (short) (clientData.getRxSequence()+1);
|
||||
if (sequenced.getSequence() != rx) {
|
||||
if (sequenced.getSequence() > rx)
|
||||
packetSender.sendRaw(new OutOfOrder(sequenced.getSequence()));
|
||||
Log.e("Invalid Sequence! Expected: " + rx + " Actual: " + sequenced.getSequence());
|
||||
return false;
|
||||
}
|
||||
clientData.setRxSequence(sequenced.getSequence());
|
||||
packetSender.sendRaw(new Acknowledge(rx));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void onDataChannel(DataChannel dataChannel) {
|
||||
if (!validateSequenced(dataChannel))
|
||||
return;
|
||||
for (byte [] data : dataChannel.getPackets()) {
|
||||
onSWGPacket(data);
|
||||
}
|
||||
}
|
||||
|
||||
private void onFragmented(IncomingPacket incoming, Fragmented frag) {
|
||||
if (!validateSequenced(frag))
|
||||
return;
|
||||
byte [] combined = fragmentedProcessor.addFragmented(frag);
|
||||
if (combined != null)
|
||||
process(incoming, combined);
|
||||
}
|
||||
|
||||
private void onAcknowledge(Acknowledge ack) {
|
||||
clientData.setLastAcknowledgement(System.nanoTime());
|
||||
clientData.setAckSequence(ack.getSequence());
|
||||
}
|
||||
|
||||
private void onOutOfOrder(OutOfOrder ooo) {
|
||||
clientData.setOOOSequence(ooo.getSequence());
|
||||
}
|
||||
|
||||
private void onSWGPacket(byte [] data) {
|
||||
data = interceptor.interceptClient(data);
|
||||
processorIntentChain.broadcastAfter(new ClientToServerPacketIntent(data));
|
||||
}
|
||||
|
||||
private void setConnectionState(ClientConnectionStatus status) {
|
||||
ClientConnectionStatus old = clientData.setStatus(status);
|
||||
if (old != status) {
|
||||
Log.i("Client Status: %s -> %s", old, status);
|
||||
processorIntentChain.broadcastAfter(new ClientConnectionChangedIntent(old, status));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package com.projectswg.networking.client.receiver;
|
||||
|
||||
import com.projectswg.common.control.Service;
|
||||
import com.projectswg.intents.ClientConnectionChangedIntent;
|
||||
import com.projectswg.networking.NetInterceptor;
|
||||
import com.projectswg.networking.client.ClientData;
|
||||
import com.projectswg.networking.client.ClientPacketSender;
|
||||
import com.projectswg.networking.client.ClientServerSocket.IncomingPacket;
|
||||
import com.projectswg.resources.ClientConnectionStatus;
|
||||
|
||||
public class ClientInboundService extends Service {
|
||||
|
||||
private final ClientInboundProcessor processor;
|
||||
|
||||
public ClientInboundService(NetInterceptor interceptor, ClientData data, ClientPacketSender packetSender) {
|
||||
this.processor = new ClientInboundProcessor(interceptor, data, packetSender);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean initialize() {
|
||||
registerForIntent(ClientConnectionChangedIntent.class, ccci -> onConnectionChanged(ccci.getStatus()));
|
||||
return super.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean start() {
|
||||
processor.start(getIntentManager());
|
||||
return super.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stop() {
|
||||
processor.stop();
|
||||
return super.stop();
|
||||
}
|
||||
|
||||
public void addPacket(IncomingPacket packet) {
|
||||
processor.addPacket(packet);
|
||||
}
|
||||
|
||||
public void disconnect() {
|
||||
processor.disconnect();
|
||||
}
|
||||
|
||||
private void onConnectionChanged(ClientConnectionStatus status) {
|
||||
switch (status) {
|
||||
case LOGIN_CONNECTED:
|
||||
case ZONE_CONNECTED:
|
||||
processor.onConnected();
|
||||
break;
|
||||
case DISCONNECTED:
|
||||
processor.onDisconnected();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package com.projectswg.networking.client.sender;
|
||||
|
||||
import com.projectswg.common.control.Service;
|
||||
import com.projectswg.networking.NetInterceptor;
|
||||
import com.projectswg.networking.client.ClientData;
|
||||
import com.projectswg.networking.client.ClientPacketSender;
|
||||
|
||||
public class ClientOutboundService extends Service {
|
||||
|
||||
private final ClientPackager packager;
|
||||
|
||||
public ClientOutboundService(ClientData data, NetInterceptor interceptor, ClientPacketSender sender) {
|
||||
this.packager = new ClientPackager(interceptor, data, sender);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean start() {
|
||||
packager.start(getIntentManager());
|
||||
return super.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stop() {
|
||||
packager.stop();
|
||||
return super.stop();
|
||||
}
|
||||
|
||||
public void sendSequential(byte [] data) {
|
||||
packager.addToPackage(data);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,219 +0,0 @@
|
||||
package com.projectswg.networking.client.sender;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import com.projectswg.common.concurrency.Delay;
|
||||
import com.projectswg.common.concurrency.PswgBasicThread;
|
||||
import com.projectswg.common.control.IntentManager;
|
||||
import com.projectswg.common.debug.Assert;
|
||||
import com.projectswg.intents.ClientConnectionChangedIntent;
|
||||
import com.projectswg.networking.NetInterceptor;
|
||||
import com.projectswg.networking.client.ClientData;
|
||||
import com.projectswg.networking.client.ClientPacketSender;
|
||||
import com.projectswg.networking.soe.DataChannel;
|
||||
import com.projectswg.networking.soe.Fragmented;
|
||||
import com.projectswg.resources.ClientConnectionStatus;
|
||||
|
||||
public class ClientPackager {
|
||||
|
||||
private final NetInterceptor interceptor;
|
||||
private final ClientPacketResender packetResender;
|
||||
private final PackagingWrapper packager;
|
||||
|
||||
public ClientPackager(NetInterceptor interceptor, ClientData clientData, ClientPacketSender sender) {
|
||||
this.interceptor = interceptor;
|
||||
this.packetResender = new ClientPacketResender(sender);
|
||||
this.packager = new PackagingWrapper(packetResender, clientData);
|
||||
}
|
||||
|
||||
public void start(IntentManager intentManager) {
|
||||
packetResender.start(intentManager);
|
||||
packager.start();
|
||||
intentManager.registerForIntent(ClientConnectionChangedIntent.class, ccci -> handleClientStatusChanged(ccci.getStatus()));
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
packager.stop();
|
||||
packetResender.stop();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
packager.reset();
|
||||
}
|
||||
|
||||
public void addToPackage(byte [] data) {
|
||||
Assert.test(data.length > 0, "Array length must be greater than 0!");
|
||||
packager.add(interceptor.interceptServer(data));
|
||||
}
|
||||
|
||||
private void handleClientStatusChanged(ClientConnectionStatus status) {
|
||||
clear();
|
||||
}
|
||||
|
||||
private static class PackagingWrapper {
|
||||
|
||||
private final AtomicBoolean running;
|
||||
private final PswgBasicThread packagerThread;
|
||||
private final ClientPacketResender packetResender;
|
||||
private final Packager packager;
|
||||
private final Queue<byte []> inboundQueue;
|
||||
private final Lock inboundQueueLock;
|
||||
private final Condition inboundQueueCondition;
|
||||
|
||||
public PackagingWrapper(ClientPacketResender packetResender, ClientData clientData) {
|
||||
this.running = new AtomicBoolean(false);
|
||||
this.packagerThread = new PswgBasicThread("packet-packager", this::loop);
|
||||
this.packetResender = packetResender;
|
||||
this.packager = new Packager(clientData);
|
||||
this.inboundQueue = new ArrayDeque<>(128);
|
||||
this.inboundQueueLock = new ReentrantLock(false);
|
||||
this.inboundQueueCondition = inboundQueueLock.newCondition();
|
||||
}
|
||||
|
||||
public void start() {
|
||||
running.set(true);
|
||||
packagerThread.start();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
running.set(false);
|
||||
packagerThread.stop(true);
|
||||
packagerThread.awaitTermination(500);
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
inboundQueue.clear();
|
||||
}
|
||||
|
||||
public void add(byte [] packet) {
|
||||
inboundQueueLock.lock();
|
||||
try {
|
||||
inboundQueue.add(packet);
|
||||
inboundQueueCondition.signal();
|
||||
} finally {
|
||||
inboundQueueLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void loop() {
|
||||
Queue<SequencedOutbound> outboundQueue = new ArrayDeque<>(64);
|
||||
while (running.get()) {
|
||||
if (!waitForInbound())
|
||||
break;
|
||||
processInbound(outboundQueue);
|
||||
Delay.sleepMicro(25); // Accumulates some packets
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the queue to be non-empty and acquires the lock
|
||||
* @return TRUE if the lock is acquired and the queue is non-empty, FALSE otherwise
|
||||
*/
|
||||
private boolean waitForInbound() {
|
||||
inboundQueueLock.lock();
|
||||
while (inboundQueue.isEmpty()) {
|
||||
try {
|
||||
inboundQueueCondition.await();
|
||||
} catch (InterruptedException e) {
|
||||
running.set(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void processInbound(Queue<SequencedOutbound> outboundQueue) {
|
||||
try {
|
||||
packager.handle(inboundQueue, outboundQueue);
|
||||
} finally {
|
||||
inboundQueueLock.unlock();
|
||||
}
|
||||
|
||||
while (!outboundQueue.isEmpty()) {
|
||||
packetResender.add(outboundQueue.poll());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class Packager {
|
||||
|
||||
private final AtomicInteger size;
|
||||
private final DataChannel channel;
|
||||
private final ClientData clientData;
|
||||
|
||||
public Packager(ClientData clientData) {
|
||||
this.size = new AtomicInteger(8);
|
||||
this.channel = new DataChannel();
|
||||
this.clientData = clientData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the inbound queue, and then sends it to the outbound queue
|
||||
* @param inboundQueue inbound queue from the server
|
||||
* @param outboundQueue outbound queue to the client
|
||||
*/
|
||||
public void handle(Queue<byte []> inboundQueue, Queue<SequencedOutbound> outboundQueue) {
|
||||
byte [] packet;
|
||||
int packetSize;
|
||||
|
||||
while (!inboundQueue.isEmpty()) {
|
||||
packet = inboundQueue.poll();
|
||||
packetSize = getPacketLength(packet);
|
||||
|
||||
if (size.get() + packetSize >= 496) // overflowed previous packet
|
||||
sendDataChannel(outboundQueue);
|
||||
|
||||
if (packetSize < 496) {
|
||||
addToDataChannel(packet, packetSize);
|
||||
} else {
|
||||
sendFragmented(outboundQueue, packet);
|
||||
}
|
||||
}
|
||||
sendDataChannel(outboundQueue);
|
||||
}
|
||||
|
||||
private void addToDataChannel(byte [] packet, int packetSize) {
|
||||
channel.addPacket(packet);
|
||||
size.getAndAdd(packetSize);
|
||||
}
|
||||
|
||||
private void sendDataChannel(Queue<SequencedOutbound> outboundQueue) {
|
||||
if (channel.getPacketCount() == 0)
|
||||
return;
|
||||
|
||||
channel.setSequence(clientData.getAndIncrementTxSequence());
|
||||
outboundQueue.add(new SequencedOutbound(channel.getSequence(), channel.encode().array()));
|
||||
reset();
|
||||
}
|
||||
|
||||
private void sendFragmented(Queue<SequencedOutbound> outboundQueue, byte [] packet) {
|
||||
Fragmented [] frags = Fragmented.encode(ByteBuffer.wrap(packet), clientData.getTxSequence());
|
||||
clientData.setTxSequence((short) (clientData.getTxSequence() + frags.length));
|
||||
for (Fragmented frag : frags) {
|
||||
outboundQueue.add(new SequencedOutbound(frag.getSequence(), frag.encode().array()));
|
||||
}
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
channel.clearPackets();
|
||||
size.set(8);
|
||||
}
|
||||
|
||||
private static int getPacketLength(byte [] data) {
|
||||
int len = data.length;
|
||||
if (len >= 255)
|
||||
return len + 3;
|
||||
return len + 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,344 +0,0 @@
|
||||
package com.projectswg.networking.client.sender;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import com.projectswg.common.concurrency.Delay;
|
||||
import com.projectswg.common.concurrency.PswgBasicThread;
|
||||
import com.projectswg.common.control.IntentManager;
|
||||
import com.projectswg.common.debug.Log;
|
||||
import com.projectswg.intents.ClientConnectionChangedIntent;
|
||||
import com.projectswg.intents.ClientSonyPacketIntent;
|
||||
import com.projectswg.networking.Packet;
|
||||
import com.projectswg.networking.client.ClientPacketSender;
|
||||
import com.projectswg.networking.soe.Acknowledge;
|
||||
import com.projectswg.networking.soe.OutOfOrder;
|
||||
import com.projectswg.resources.ClientConnectionStatus;
|
||||
|
||||
/**
|
||||
* This class is in charge of resending any unacknowledged packets
|
||||
*/
|
||||
public class ClientPacketResender {
|
||||
|
||||
private final ResenderWrapper resender;
|
||||
private final PswgBasicThread executor;
|
||||
private final AtomicBoolean running;
|
||||
|
||||
public ClientPacketResender(ClientPacketSender sender) {
|
||||
this.resender = new ResenderWrapper(sender);
|
||||
this.executor = new PswgBasicThread("packet-resender", this::resender);
|
||||
this.running = new AtomicBoolean(false);
|
||||
}
|
||||
|
||||
public void start(IntentManager intentManager) {
|
||||
intentManager.registerForIntent(ClientSonyPacketIntent.class, cspi -> handleClientPacket(cspi.getPacket()));
|
||||
intentManager.registerForIntent(ClientConnectionChangedIntent.class, ccci -> handleClientStatusChanged(ccci.getStatus()));
|
||||
|
||||
running.set(true);
|
||||
executor.start();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
running.set(false);
|
||||
executor.stop(true);
|
||||
executor.awaitTermination(500);
|
||||
resender.reset();
|
||||
}
|
||||
|
||||
public void add(SequencedOutbound out) {
|
||||
resender.add(out);
|
||||
}
|
||||
|
||||
private void resender() {
|
||||
while (running.get()) {
|
||||
resender.resend();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleClientPacket(Packet p) {
|
||||
if (p instanceof Acknowledge)
|
||||
resender.onAcknowledge(((Acknowledge) p).getSequence());
|
||||
else if (p instanceof OutOfOrder)
|
||||
resender.onOutOfOrder(((OutOfOrder) p).getSequence());
|
||||
}
|
||||
|
||||
private void handleClientStatusChanged(ClientConnectionStatus status) {
|
||||
resender.reset();
|
||||
}
|
||||
|
||||
private static class ResenderWrapper {
|
||||
|
||||
private final List<SequencedOutbound> sentPackets;
|
||||
private final CongestionAvoidance congAvoidance;
|
||||
private final Lock sentPacketsLock;
|
||||
private final Condition sentPacketsCondition;
|
||||
private final Resender resender;
|
||||
|
||||
public ResenderWrapper(ClientPacketSender sender) {
|
||||
this.sentPackets = new LinkedList<>();
|
||||
this.congAvoidance = new CongestionAvoidance();
|
||||
this.sentPacketsLock = new ReentrantLock(false);
|
||||
this.sentPacketsCondition = sentPacketsLock.newCondition();
|
||||
this.resender = new Resender(congAvoidance, sender);
|
||||
}
|
||||
|
||||
public void add(SequencedOutbound seq) {
|
||||
sentPacketsLock.lock();
|
||||
try {
|
||||
sentPackets.add(seq);
|
||||
sentPacketsCondition.signal();
|
||||
} finally {
|
||||
sentPacketsLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
sentPacketsLock.lock();
|
||||
try {
|
||||
sentPackets.clear();
|
||||
} finally {
|
||||
sentPacketsLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void onOutOfOrder(short sequence) {
|
||||
Log.w("OOO %d", sequence);
|
||||
sentPacketsLock.lock();
|
||||
try {
|
||||
congAvoidance.onOutOfOrder();
|
||||
} finally {
|
||||
sentPacketsLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void onAcknowledge(short sequence) {
|
||||
sentPacketsLock.lock();
|
||||
try {
|
||||
int seqInt = sequence & 0xFFFF;
|
||||
SequencedOutbound out;
|
||||
while (!sentPackets.isEmpty()) {
|
||||
out = sentPackets.get(0);
|
||||
if (out.getSequenceInt() <= seqInt || (seqInt < 100 && out.getSequenceInt() > Short.MAX_VALUE-100)) {
|
||||
sentPackets.remove(0);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
congAvoidance.onAcknowledgement();
|
||||
} finally {
|
||||
sentPacketsLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void resend() {
|
||||
sentPacketsLock.lock();
|
||||
try {
|
||||
waitForPacket();
|
||||
resender.handle(sentPackets);
|
||||
} finally {
|
||||
sentPacketsLock.unlock();
|
||||
}
|
||||
waitForTimeoutOrAck();
|
||||
}
|
||||
|
||||
private void waitForPacket() {
|
||||
if (!sentPackets.isEmpty())
|
||||
return;
|
||||
try {
|
||||
while (sentPackets.isEmpty()) {
|
||||
sentPacketsCondition.await();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
|
||||
}
|
||||
congAvoidance.clearTimeSinceSent();
|
||||
}
|
||||
|
||||
private void waitForTimeoutOrAck() {
|
||||
double avg = congAvoidance.getAverageRTT();
|
||||
if (avg == Double.MAX_VALUE)
|
||||
Delay.sleepMicro(5);
|
||||
else
|
||||
Delay.sleepNano((long) Math.min(1E9, Math.max(5E6, avg)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class Resender {
|
||||
|
||||
private final CongestionAvoidance congAvoidance;
|
||||
private final ClientPacketSender sender;
|
||||
|
||||
public Resender(CongestionAvoidance congAvoidance, ClientPacketSender sender) {
|
||||
this.congAvoidance = congAvoidance;
|
||||
this.sender = sender;
|
||||
}
|
||||
|
||||
public void handle(List<SequencedOutbound> sentPackets) {
|
||||
congAvoidance.markBeginningOfWindow();
|
||||
int sentPacketCount = sentPackets.size();
|
||||
boolean lossEvent = sentPacketCount > 0 && (congAvoidance.isTimedOut() || congAvoidance.isTripleACK());
|
||||
|
||||
printStatus(lossEvent);
|
||||
sendAllInWindow(sentPackets);
|
||||
if (sentPacketCount < 100) {
|
||||
casualWindowIteration(lossEvent);
|
||||
} else {
|
||||
intenseWindowIteration(lossEvent, sentPacketCount);
|
||||
}
|
||||
congAvoidance.markEndOfWindow();
|
||||
}
|
||||
|
||||
private void printStatus(boolean lossEvent) {
|
||||
if (congAvoidance.getWindow() != 50 && congAvoidance.getAverageRTT() != Double.MAX_VALUE)
|
||||
Log.d("Resender: Congestion Window: %d Average RTT: %.3fms", congAvoidance.getWindow(), congAvoidance.getAverageRTT()/1E6);
|
||||
|
||||
if (lossEvent)
|
||||
Log.w("Resender: Loss Event!");
|
||||
}
|
||||
|
||||
/**
|
||||
* This congestion avoidance algorithm focuses more on slow window
|
||||
* increases/decreases. This is ideal for casual gameplay where there
|
||||
* aren't many packets being sent at once.
|
||||
*/
|
||||
private void casualWindowIteration(boolean lossEvent) {
|
||||
if (lossEvent) {
|
||||
congAvoidance.setWindow(Math.max(10, congAvoidance.getWindow() - 5));
|
||||
} else {
|
||||
congAvoidance.setWindow(Math.min(50, congAvoidance.getWindow() + 5));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This congestion avoidance algorithm focuses on fast window
|
||||
* increases/decreases. This is ideal for zone-in where as many packets as
|
||||
* possible need to be sent as fast as possible.
|
||||
*/
|
||||
private void intenseWindowIteration(boolean lossEvent, int sentPackets) {
|
||||
int window = congAvoidance.getWindow();
|
||||
if (window > sentPackets)
|
||||
return;
|
||||
if (lossEvent) {
|
||||
congAvoidance.setWindow(Math.max(50, window / 4));
|
||||
} else {
|
||||
congAvoidance.setWindow((int) (window * 1.5));
|
||||
}
|
||||
}
|
||||
|
||||
private void sendAllInWindow(List<SequencedOutbound> sentPackets) {
|
||||
int max = congAvoidance.getWindow();
|
||||
for (SequencedOutbound out : sentPackets) {
|
||||
if (--max < 0)
|
||||
break;
|
||||
sender.sendRaw(out.getData());
|
||||
congAvoidance.onSentPacket();
|
||||
}
|
||||
congAvoidance.updateTimeSinceSent();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class CongestionAvoidance {
|
||||
|
||||
// Higher-level properties
|
||||
private long lastSent;
|
||||
private double averageRtt;
|
||||
private int congestionWindow;
|
||||
|
||||
// Lower-level properties
|
||||
private int outOfOrders;
|
||||
private int acknowledgements;
|
||||
private int sentPackets;
|
||||
private int missedWindows;
|
||||
|
||||
public CongestionAvoidance() {
|
||||
this.averageRtt = Double.MAX_VALUE;
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
this.lastSent = 0;
|
||||
this.congestionWindow = 1;
|
||||
|
||||
this.outOfOrders = 0;
|
||||
this.acknowledgements = 0;
|
||||
this.sentPackets = 0;
|
||||
this.missedWindows = 0;
|
||||
}
|
||||
|
||||
public void onOutOfOrder() {
|
||||
updateRtt();
|
||||
this.outOfOrders++;
|
||||
}
|
||||
|
||||
public void onAcknowledgement() {
|
||||
updateRtt();
|
||||
this.outOfOrders = 0;
|
||||
this.acknowledgements++;
|
||||
}
|
||||
|
||||
public void onSentPacket() {
|
||||
this.sentPackets++;
|
||||
}
|
||||
|
||||
public void markBeginningOfWindow() {
|
||||
if (acknowledgements == 0 && sentPackets > 0)
|
||||
missedWindows++;
|
||||
else
|
||||
missedWindows = 0;
|
||||
this.sentPackets = 0;
|
||||
}
|
||||
|
||||
public void markEndOfWindow() {
|
||||
this.acknowledgements = 0;
|
||||
}
|
||||
|
||||
public void setWindow(int congestionWindow) {
|
||||
this.congestionWindow = congestionWindow;
|
||||
}
|
||||
|
||||
public double getAverageRTT() {
|
||||
return averageRtt;
|
||||
}
|
||||
|
||||
public int getWindow() {
|
||||
return congestionWindow;
|
||||
}
|
||||
|
||||
public boolean isTripleACK() {
|
||||
return outOfOrders >= 3;
|
||||
}
|
||||
|
||||
public boolean isTimedOut() {
|
||||
return missedWindows >= 2 && averageRtt != Double.MAX_VALUE;
|
||||
}
|
||||
|
||||
public void updateTimeSinceSent() {
|
||||
if (lastSent == 0)
|
||||
lastSent = System.nanoTime();
|
||||
}
|
||||
|
||||
private void updateRtt() {
|
||||
long lastRtt = lastSent;
|
||||
lastSent = 0;
|
||||
if (lastRtt <= 0)
|
||||
return;
|
||||
lastRtt = System.nanoTime() - lastRtt;
|
||||
|
||||
if (this.averageRtt == Double.MAX_VALUE)
|
||||
this.averageRtt = lastRtt;
|
||||
else
|
||||
this.averageRtt = averageRtt * 0.875 + lastRtt * 0.125;
|
||||
}
|
||||
|
||||
public void clearTimeSinceSent() {
|
||||
lastSent = 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package com.projectswg.networking.client.sender;
|
||||
|
||||
import com.projectswg.networking.soe.SequencedPacket;
|
||||
|
||||
public class SequencedOutbound implements SequencedPacket {
|
||||
|
||||
private final int sequence;
|
||||
private final byte [] data;
|
||||
|
||||
public SequencedOutbound(short sequence, byte [] data) {
|
||||
this.sequence = sequence & 0xFFFF;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getSequence() { return (short) sequence; }
|
||||
public int getSequenceInt() { return sequence; }
|
||||
public byte [] getData() { return data; }
|
||||
|
||||
@Override
|
||||
public int compareTo(SequencedPacket p) {
|
||||
if (getSequence() < p.getSequence())
|
||||
return -1;
|
||||
if (getSequence() == p.getSequence())
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof SequencedOutbound))
|
||||
return super.equals(o);
|
||||
return ((SequencedOutbound) o).getSequence() == sequence;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return sequence;
|
||||
}
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
package com.projectswg.networking.server;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import com.projectswg.common.concurrency.Delay;
|
||||
import com.projectswg.common.concurrency.PswgBasicScheduledThread;
|
||||
import com.projectswg.common.concurrency.PswgBasicThread;
|
||||
import com.projectswg.common.control.IntentChain;
|
||||
import com.projectswg.common.control.IntentManager;
|
||||
import com.projectswg.common.control.Service;
|
||||
import com.projectswg.common.debug.Assert;
|
||||
import com.projectswg.common.debug.Log;
|
||||
import com.projectswg.common.network.packets.swg.zone.HeartBeat;
|
||||
import com.projectswg.connection.HolocoreSocket;
|
||||
import com.projectswg.connection.ServerConnectionChangedReason;
|
||||
import com.projectswg.connection.packets.RawPacket;
|
||||
import com.projectswg.intents.ClientConnectionChangedIntent;
|
||||
import com.projectswg.intents.ClientToServerPacketIntent;
|
||||
import com.projectswg.intents.ServerConnectionChangedIntent;
|
||||
import com.projectswg.intents.ServerToClientPacketIntent;
|
||||
|
||||
public class ServerConnectionService extends Service {
|
||||
|
||||
private static final long HOLOCORE_TIMEOUT = TimeUnit.SECONDS.toNanos(21);
|
||||
|
||||
private final HolocoreSocket socket;
|
||||
private final ConnectionThread connectionThread;
|
||||
private IntentChain socketIntentChain;
|
||||
|
||||
public ServerConnectionService(InetAddress addr, int port) {
|
||||
this.socket = new HolocoreSocket(addr, port);
|
||||
this.connectionThread = new ConnectionThread(socket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean initialize() {
|
||||
socketIntentChain = new IntentChain(getIntentManager());
|
||||
connectionThread.initialize(getIntentManager());
|
||||
this.socket.setStatusChangedCallback((o, n, reason) -> socketIntentChain.broadcastAfter(new ServerConnectionChangedIntent(o, n, reason)));
|
||||
registerForIntent(ClientConnectionChangedIntent.class, ccci -> processClientConnectionChanged(ccci));
|
||||
registerForIntent(ClientToServerPacketIntent.class, ctspi -> send(ctspi.getData()));
|
||||
return super.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean terminate() {
|
||||
socket.terminate();
|
||||
stopServer();
|
||||
return super.terminate();
|
||||
}
|
||||
|
||||
public void setRemoteAddress(InetAddress addr, int port) {
|
||||
socket.setRemoteAddress(addr, port);
|
||||
}
|
||||
|
||||
public InetSocketAddress getRemoteAddress() {
|
||||
return socket.getRemoteAddress();
|
||||
}
|
||||
|
||||
public boolean send(byte [] raw) {
|
||||
if (!socket.isConnected()) {
|
||||
connectionThread.addToOutQueue(raw);
|
||||
return false;
|
||||
}
|
||||
return socket.send(raw);
|
||||
}
|
||||
|
||||
private void processClientConnectionChanged(ClientConnectionChangedIntent ccci) {
|
||||
Log.d("processClientConnectionChanged(%s)", ccci.getStatus());
|
||||
switch (ccci.getStatus()) {
|
||||
case LOGIN_CONNECTED:
|
||||
startServer();
|
||||
break;
|
||||
case DISCONNECTED:
|
||||
stopServer();
|
||||
connectionThread.reset();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void startServer() {
|
||||
connectionThread.start();
|
||||
}
|
||||
|
||||
private void stopServer() {
|
||||
connectionThread.stop();
|
||||
}
|
||||
|
||||
private static class ConnectionThread {
|
||||
|
||||
private final HolocoreSocket connection;
|
||||
private final AtomicLong lastHeartbeat;
|
||||
private final PswgBasicThread thread;
|
||||
private final PswgBasicScheduledThread heartbeatThread;
|
||||
private final Queue<byte []> outQueue;
|
||||
private IntentChain recvIntentChain;
|
||||
|
||||
public ConnectionThread(HolocoreSocket connection) {
|
||||
this.connection = connection;
|
||||
this.lastHeartbeat = new AtomicLong(0);
|
||||
this.thread = new PswgBasicThread("server-connection", this::run);
|
||||
this.heartbeatThread = new PswgBasicScheduledThread("server-heartbeat", this::heartbeat);
|
||||
this.outQueue = new LinkedList<>();
|
||||
this.recvIntentChain = null;
|
||||
}
|
||||
|
||||
public void initialize(IntentManager intentManager) {
|
||||
this.recvIntentChain = new IntentChain(intentManager);
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
outQueue.clear();
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (thread.isRunning())
|
||||
return;
|
||||
thread.start();
|
||||
heartbeatThread.startWithFixedDelay(0, 10*1000);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (!thread.isRunning())
|
||||
return;
|
||||
thread.stop(true);
|
||||
heartbeatThread.stop();
|
||||
thread.awaitTermination(5000);
|
||||
heartbeatThread.awaitTermination(1000);
|
||||
}
|
||||
|
||||
public void addToOutQueue(byte [] raw) {
|
||||
synchronized (outQueue) {
|
||||
outQueue.add(raw);
|
||||
}
|
||||
}
|
||||
|
||||
private void heartbeat() {
|
||||
if (connection.isConnected()) {
|
||||
long lastHeartbeat = this.lastHeartbeat.get();
|
||||
if (lastHeartbeat != 0 && System.nanoTime() - lastHeartbeat >= HOLOCORE_TIMEOUT) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
connection.send(new HeartBeat().encode().array());
|
||||
}
|
||||
}
|
||||
|
||||
private void run() {
|
||||
Log.i("Started ServerConnection");
|
||||
try {
|
||||
while (thread.isRunning()) {
|
||||
if (connection.isDisconnected() && !tryConnect()) {
|
||||
Delay.sleepMilli(1000);
|
||||
continue;
|
||||
}
|
||||
Assert.test(connection.isConnected());
|
||||
RawPacket packet = null;
|
||||
while ((packet = connection.receive()) != null) {
|
||||
if (packet.getCrc() == HeartBeat.CRC)
|
||||
lastHeartbeat.set(System.nanoTime());
|
||||
recvIntentChain.broadcastAfter(new ServerToClientPacketIntent(packet.getCrc(), packet.getData()));
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
Log.e(t);
|
||||
} finally {
|
||||
connection.disconnect(ServerConnectionChangedReason.NONE);
|
||||
}
|
||||
Log.i("Stopped ServerConnection");
|
||||
}
|
||||
|
||||
private boolean tryConnect() {
|
||||
if (!connection.connect(10000))
|
||||
return false;
|
||||
synchronized (outQueue) {
|
||||
while (!outQueue.isEmpty()) {
|
||||
if (!connection.send(outQueue.poll()))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package com.projectswg.networking.server;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
import com.projectswg.common.control.IntentManager;
|
||||
import com.projectswg.connection.ServerConnectionStatus;
|
||||
import com.projectswg.intents.ClientConnectionChangedIntent;
|
||||
import com.projectswg.intents.ServerConnectionChangedIntent;
|
||||
import com.projectswg.intents.ServerToClientPacketIntent;
|
||||
import com.projectswg.resources.ClientConnectionStatus;
|
||||
|
||||
public class ServerConnectionWrapper {
|
||||
|
||||
private ServerConnectionService connection;
|
||||
private ConnectionCallback callback;
|
||||
private IntentManager intentManager;
|
||||
|
||||
public ServerConnectionWrapper(InetAddress addr, int port) {
|
||||
connection = new ServerConnectionService(addr, port);
|
||||
intentManager = null;
|
||||
}
|
||||
|
||||
public void setConnectionCallback(ConnectionCallback callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
public void connect() {
|
||||
setupConnectionIntents();
|
||||
connection.initialize();
|
||||
connection.start();
|
||||
new ClientConnectionChangedIntent(ClientConnectionStatus.DISCONNECTED, ClientConnectionStatus.LOGIN_CONNECTED).broadcast(connection.getIntentManager());
|
||||
}
|
||||
|
||||
public void disconnect() {
|
||||
new ClientConnectionChangedIntent(ClientConnectionStatus.LOGIN_CONNECTED, ClientConnectionStatus.DISCONNECTED).broadcast(connection.getIntentManager());
|
||||
connection.stop();
|
||||
connection.terminate();
|
||||
connection.getIntentManager().terminate();
|
||||
}
|
||||
|
||||
public void send(byte [] data) {
|
||||
connection.send(data);
|
||||
}
|
||||
|
||||
private void setupConnectionIntents() {
|
||||
intentManager = new IntentManager(Runtime.getRuntime().availableProcessors());
|
||||
connection.setIntentManager(intentManager);
|
||||
intentManager.registerForIntent(ServerConnectionChangedIntent.class, scci -> onServerConnectionChanged(scci));
|
||||
intentManager.registerForIntent(ServerToClientPacketIntent.class, scpi -> onServerData(scpi));
|
||||
}
|
||||
|
||||
private void onServerConnectionChanged(ServerConnectionChangedIntent i) {
|
||||
if (callback != null)
|
||||
callback.onServerConnectionChanged(i.getOldStatus(), i.getStatus());
|
||||
}
|
||||
|
||||
private void onServerData(ServerToClientPacketIntent i) {
|
||||
if (callback != null)
|
||||
callback.onServerPacket(i.getCrc(), i.getRawData());
|
||||
}
|
||||
|
||||
public interface ConnectionCallback {
|
||||
void onServerConnectionChanged(ServerConnectionStatus old, ServerConnectionStatus status);
|
||||
void onServerPacket(int crc, byte [] data);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.projectswg.resources;
|
||||
|
||||
public enum ClientConnectionStatus {
|
||||
DISCONNECTED,
|
||||
LOGIN_CONNECTED,
|
||||
ZONE_CONNECTED
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package com.projectswg.resources;
|
||||
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
public class HolocorePreferences {
|
||||
|
||||
private static final HolocorePreferences INSTANCE = new HolocorePreferences();
|
||||
private static final String NODE_NAME = "/com/projectswg/SWG_Forwarder";
|
||||
|
||||
private final Preferences pref;
|
||||
|
||||
private HolocorePreferences() {
|
||||
pref = Preferences.userRoot().node(NODE_NAME);
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return pref.get(PreferenceKeys.USERNAME.getKey(), "");
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
pref.put(PreferenceKeys.USERNAME.getKey(), username);
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return pref.get(PreferenceKeys.PASSWORD.getKey(), "");
|
||||
}
|
||||
|
||||
public void setPassword(String username) {
|
||||
pref.put(PreferenceKeys.PASSWORD.getKey(), username);
|
||||
}
|
||||
|
||||
public static HolocorePreferences getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private enum PreferenceKeys {
|
||||
USERNAME ("USERNAME"),
|
||||
PASSWORD ("PASSWORD");
|
||||
|
||||
private String key;
|
||||
|
||||
PreferenceKeys(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
package com.projectswg.services;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import com.projectswg.common.control.Service;
|
||||
import com.projectswg.common.debug.Assert;
|
||||
import com.projectswg.common.debug.Log;
|
||||
import com.projectswg.connection.ServerConnectionStatus;
|
||||
import com.projectswg.intents.ClientToServerPacketIntent;
|
||||
import com.projectswg.intents.ServerConnectionChangedIntent;
|
||||
import com.projectswg.intents.ServerToClientPacketIntent;
|
||||
import com.projectswg.recording.PacketRecorder;
|
||||
|
||||
public class PacketRecordingService extends Service {
|
||||
|
||||
private final Object recordingMutex;
|
||||
|
||||
private File recorderFile;
|
||||
private PacketRecorder recorder;
|
||||
private ServerConnectionStatus serverStatus;
|
||||
private InetSocketAddress source;
|
||||
private InetSocketAddress destination;
|
||||
private boolean recording;
|
||||
|
||||
public PacketRecordingService() {
|
||||
this.recordingMutex = new Object();
|
||||
this.recorderFile = null;
|
||||
this.recorder = null;
|
||||
this.serverStatus = ServerConnectionStatus.DISCONNECTED;
|
||||
this.source = null;
|
||||
this.destination = null;
|
||||
this.recording = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean initialize() {
|
||||
registerForIntent(ServerToClientPacketIntent.class, stcpi -> onServerToClient(stcpi));
|
||||
registerForIntent(ClientToServerPacketIntent.class, ctspi -> onClientToServer(ctspi));
|
||||
registerForIntent(ServerConnectionChangedIntent.class, scci -> onServerStatusChanged(scci));
|
||||
return super.initialize();
|
||||
}
|
||||
|
||||
public void setAddress(InetSocketAddress source, InetSocketAddress destination) {
|
||||
this.source = source;
|
||||
this.destination = destination;
|
||||
}
|
||||
|
||||
private void startRecording() {
|
||||
synchronized (recordingMutex) {
|
||||
Assert.test(!recording);
|
||||
recording = true;
|
||||
try {
|
||||
recorderFile = File.createTempFile("HolocoreRecording", ".hcap");
|
||||
recorder = new PacketRecorder(recorderFile);
|
||||
} catch (IOException e) {
|
||||
Log.e(e);
|
||||
recorderFile = null;
|
||||
recorder = null;
|
||||
recording = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void stopRecording() {
|
||||
synchronized (recordingMutex) {
|
||||
Assert.test(recording);
|
||||
recording = false;
|
||||
try {
|
||||
recorder.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onServerStatusChanged(ServerConnectionChangedIntent scci) {
|
||||
this.serverStatus = scci.getStatus();
|
||||
updateRecordingState();
|
||||
}
|
||||
|
||||
private void updateRecordingState() {
|
||||
switch (serverStatus) {
|
||||
case CONNECTING:
|
||||
startRecording();
|
||||
break;
|
||||
case DISCONNECTED:
|
||||
stopRecording();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void onServerToClient(ServerToClientPacketIntent s2c) {
|
||||
synchronized (recordingMutex) {
|
||||
if (!recording)
|
||||
return;
|
||||
recorder.record(true, destination, source, s2c.getRawData());
|
||||
}
|
||||
}
|
||||
|
||||
private void onClientToServer(ClientToServerPacketIntent c2s) {
|
||||
synchronized (recordingMutex) {
|
||||
if (!recording)
|
||||
return;
|
||||
recorder.record(false, source, destination, c2s.getData());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.projectswg.forwarder;
|
||||
|
||||
import com.projectswg.forwarder.services.client.ClientConnectionManager;
|
||||
import com.projectswg.forwarder.services.crash.CrashManager;
|
||||
import com.projectswg.forwarder.services.server.ServerConnectionManager;
|
||||
import me.joshlarson.jlcommon.control.Manager;
|
||||
import me.joshlarson.jlcommon.control.ManagerStructure;
|
||||
|
||||
@ManagerStructure(children = {
|
||||
ClientConnectionManager.class,
|
||||
ServerConnectionManager.class,
|
||||
CrashManager.class
|
||||
})
|
||||
public class ConnectionManager extends Manager {
|
||||
|
||||
}
|
||||
175
src/main/java/com/projectswg/forwarder/Forwarder.java
Normal file
175
src/main/java/com/projectswg/forwarder/Forwarder.java
Normal file
@@ -0,0 +1,175 @@
|
||||
package com.projectswg.forwarder;
|
||||
|
||||
import com.projectswg.forwarder.intents.client.ClientConnectedIntent;
|
||||
import com.projectswg.forwarder.intents.client.ClientDisconnectedIntent;
|
||||
import com.projectswg.forwarder.intents.control.ClientCrashedIntent;
|
||||
import com.projectswg.forwarder.intents.control.StartForwarderIntent;
|
||||
import com.projectswg.forwarder.intents.control.StopForwarderIntent;
|
||||
import me.joshlarson.jlcommon.concurrency.Delay;
|
||||
import me.joshlarson.jlcommon.control.IntentManager;
|
||||
import me.joshlarson.jlcommon.control.Manager;
|
||||
import me.joshlarson.jlcommon.control.SafeMain;
|
||||
import me.joshlarson.jlcommon.log.Log;
|
||||
import me.joshlarson.jlcommon.log.Log.LogLevel;
|
||||
import me.joshlarson.jlcommon.log.log_wrapper.ConsoleLogWrapper;
|
||||
import me.joshlarson.jlcommon.utilities.ThreadUtilities;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
public class Forwarder {
|
||||
|
||||
private final ForwarderData data;
|
||||
private final IntentManager intentManager;
|
||||
private final AtomicBoolean connected;
|
||||
|
||||
public Forwarder() {
|
||||
this.data = new ForwarderData();
|
||||
this.intentManager = new IntentManager(false, Runtime.getRuntime().availableProcessors());
|
||||
this.connected = new AtomicBoolean(false);
|
||||
}
|
||||
|
||||
public static void main(String [] args) {
|
||||
SafeMain.main("forwarder", Forwarder::mainRunnable);
|
||||
}
|
||||
|
||||
private static void mainRunnable() {
|
||||
Log.addWrapper(new ConsoleLogWrapper(LogLevel.TRACE));
|
||||
Forwarder forwarder = new Forwarder();
|
||||
forwarder.getData().setAddress(new InetSocketAddress(44463));
|
||||
forwarder.run();
|
||||
ThreadUtilities.printActiveThreads();
|
||||
}
|
||||
|
||||
public File readClientOutput(InputStream is) {
|
||||
StringBuilder output = new StringBuilder();
|
||||
byte [] buffer = new byte[2048];
|
||||
int n;
|
||||
try {
|
||||
while ((n = is.read(buffer)) > 0) {
|
||||
output.append(new String(buffer, 0, n, StandardCharsets.UTF_8));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w("IOException while reading client output");
|
||||
Log.w(e);
|
||||
}
|
||||
return onClientClosed(output.toString());
|
||||
}
|
||||
|
||||
private File onClientClosed(String clientOutput) {
|
||||
if (!connected.get())
|
||||
return null;
|
||||
File output;
|
||||
try {
|
||||
output = Files.createTempFile("HolocoreCrashLog", ".zip").toFile();
|
||||
} catch (IOException e) {
|
||||
Log.e("Failed to write crash log! Could not create temp file.");
|
||||
return null;
|
||||
}
|
||||
try (ZipOutputStream zip = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(output)))) {
|
||||
{
|
||||
byte [] data = clientOutput.getBytes(StandardCharsets.UTF_8);
|
||||
ZipEntry entry = new ZipEntry("output.txt");
|
||||
entry.setTime(System.currentTimeMillis());
|
||||
entry.setSize(data.length);
|
||||
entry.setMethod(ZipOutputStream.DEFLATED);
|
||||
zip.putNextEntry(entry);
|
||||
zip.write(data);
|
||||
zip.closeEntry();
|
||||
}
|
||||
ClientCrashedIntent cci = new ClientCrashedIntent(zip);
|
||||
cci.broadcast(intentManager);
|
||||
long startSleep = System.nanoTime();
|
||||
while (!cci.isComplete() && System.nanoTime() - startSleep < 1E9)
|
||||
Delay.sleepMilli(10);
|
||||
return output;
|
||||
} catch (IOException e) {
|
||||
Log.e("Failed to write crash log! %s: %s", e.getClass().getName(), e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void run() {
|
||||
intentManager.initialize();
|
||||
intentManager.registerForIntent(ClientConnectedIntent.class, cci -> connected.set(true));
|
||||
intentManager.registerForIntent(ClientDisconnectedIntent.class, cdi -> connected.set(false));
|
||||
|
||||
ConnectionManager primary = new ConnectionManager();
|
||||
{
|
||||
primary.setIntentManager(intentManager);
|
||||
List<Manager> managers = Collections.singletonList(primary);
|
||||
Manager.start(managers);
|
||||
new StartForwarderIntent(data).broadcast(intentManager);
|
||||
Manager.run(managers, 100);
|
||||
new StopForwarderIntent().broadcast(intentManager);
|
||||
Manager.stop(managers);
|
||||
}
|
||||
|
||||
intentManager.terminate(false);
|
||||
primary.setIntentManager(null);
|
||||
|
||||
}
|
||||
|
||||
public ForwarderData getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public static class ForwarderData {
|
||||
|
||||
private InetSocketAddress address = null;
|
||||
private String username = null;
|
||||
private String password = null;
|
||||
private int loginPort = 0;
|
||||
private int zonePort = 0;
|
||||
|
||||
private ForwarderData() { }
|
||||
|
||||
public InetSocketAddress getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public int getLoginPort() {
|
||||
return loginPort;
|
||||
}
|
||||
|
||||
public int getZonePort() {
|
||||
return zonePort;
|
||||
}
|
||||
|
||||
public void setAddress(InetSocketAddress address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public void setLoginPort(int loginPort) {
|
||||
this.loginPort = loginPort;
|
||||
}
|
||||
|
||||
public void setZonePort(int zonePort) {
|
||||
this.zonePort = zonePort;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.projectswg.forwarder.intents.client;
|
||||
|
||||
import me.joshlarson.jlcommon.control.Intent;
|
||||
|
||||
public class ClientConnectedIntent extends Intent {
|
||||
|
||||
public ClientConnectedIntent() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.projectswg.forwarder.intents.client;
|
||||
|
||||
import me.joshlarson.jlcommon.control.Intent;
|
||||
|
||||
public class ClientDisconnectedIntent extends Intent {
|
||||
|
||||
public ClientDisconnectedIntent() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.projectswg.forwarder.intents.client;
|
||||
|
||||
import me.joshlarson.jlcommon.control.Intent;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class DataPacketInboundIntent extends Intent {
|
||||
|
||||
private final byte [] data;
|
||||
|
||||
public DataPacketInboundIntent(@Nonnull byte [] data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public byte [] getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.projectswg.forwarder.intents.client;
|
||||
|
||||
import me.joshlarson.jlcommon.control.Intent;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class DataPacketOutboundIntent extends Intent {
|
||||
|
||||
private final byte [] data;
|
||||
|
||||
public DataPacketOutboundIntent(@Nonnull byte [] data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public byte [] getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.projectswg.forwarder.intents.client;
|
||||
|
||||
import com.projectswg.forwarder.resources.networking.packets.Packet;
|
||||
import me.joshlarson.jlcommon.control.Intent;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class SonyPacketInboundIntent extends Intent {
|
||||
|
||||
private final Packet packet;
|
||||
|
||||
public SonyPacketInboundIntent(@Nonnull Packet packet) {
|
||||
this.packet = packet;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public Packet getPacket() {
|
||||
return packet;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.projectswg.forwarder.intents.client;
|
||||
|
||||
import com.projectswg.forwarder.resources.networking.data.ProtocolStack;
|
||||
import me.joshlarson.jlcommon.control.Intent;
|
||||
|
||||
import javax.annotation.CheckForNull;
|
||||
|
||||
public class UpdateStackIntent extends Intent {
|
||||
|
||||
private final ProtocolStack stack;
|
||||
|
||||
public UpdateStackIntent(ProtocolStack stack) {
|
||||
this.stack = stack;
|
||||
}
|
||||
|
||||
@CheckForNull
|
||||
public ProtocolStack getStack() {
|
||||
return stack;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.projectswg.forwarder.intents.control;
|
||||
|
||||
import me.joshlarson.jlcommon.control.Intent;
|
||||
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
public class ClientCrashedIntent extends Intent {
|
||||
|
||||
private final ZipOutputStream outputStream;
|
||||
private final Object fileMutex;
|
||||
|
||||
public ClientCrashedIntent(ZipOutputStream outputStream) {
|
||||
this.outputStream = outputStream;
|
||||
this.fileMutex = new Object();
|
||||
}
|
||||
|
||||
public ZipOutputStream getOutputStream() {
|
||||
return outputStream;
|
||||
}
|
||||
|
||||
public Object getFileMutex() {
|
||||
return fileMutex;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.projectswg.forwarder.intents.control;
|
||||
|
||||
import com.projectswg.forwarder.Forwarder.ForwarderData;
|
||||
import me.joshlarson.jlcommon.control.Intent;
|
||||
|
||||
public class StartForwarderIntent extends Intent {
|
||||
|
||||
private final ForwarderData data;
|
||||
|
||||
public StartForwarderIntent(ForwarderData data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public ForwarderData getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public static void broadcast(ForwarderData data) {
|
||||
new StartForwarderIntent(data).broadcast();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.projectswg.forwarder.intents.control;
|
||||
|
||||
import me.joshlarson.jlcommon.control.Intent;
|
||||
|
||||
public class StopForwarderIntent extends Intent {
|
||||
|
||||
public StopForwarderIntent() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.projectswg.forwarder.intents.server;
|
||||
|
||||
import me.joshlarson.jlcommon.control.Intent;
|
||||
|
||||
public class ServerConnectedIntent extends Intent {
|
||||
|
||||
public ServerConnectedIntent() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.projectswg.forwarder.intents.server;
|
||||
|
||||
import me.joshlarson.jlcommon.control.Intent;
|
||||
|
||||
public class ServerDisconnectedIntent extends Intent {
|
||||
|
||||
public ServerDisconnectedIntent() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.projectswg.forwarder.resources.client.state;
|
||||
|
||||
import me.joshlarson.jlcommon.log.Log;
|
||||
|
||||
public class OutboundDataTuner {
|
||||
|
||||
private long start;
|
||||
private int maxSend;
|
||||
private int outOfOrders;
|
||||
|
||||
public OutboundDataTuner() {
|
||||
reset();
|
||||
}
|
||||
|
||||
public int getMaxSend() {
|
||||
return maxSend;
|
||||
}
|
||||
|
||||
public void markStart() {
|
||||
reset();
|
||||
start = System.nanoTime();
|
||||
}
|
||||
|
||||
public void markEnd() {
|
||||
long time = System.nanoTime() - start;
|
||||
Log.d("Max Send=%d Time to Zone=%.0fms OOO=%d", maxSend, time/1E6, outOfOrders);
|
||||
reset();
|
||||
}
|
||||
|
||||
public void markOOO() {
|
||||
outOfOrders++;
|
||||
maxSend -= 2;
|
||||
capMaxSend();
|
||||
}
|
||||
|
||||
public void markAck() {
|
||||
maxSend += 2;
|
||||
capMaxSend();
|
||||
}
|
||||
|
||||
public void markLag() {
|
||||
maxSend -= 50;
|
||||
capMaxSend();
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
start = 0;
|
||||
outOfOrders = 0;
|
||||
maxSend = 200;
|
||||
}
|
||||
|
||||
private void capMaxSend() {
|
||||
if (maxSend < 100)
|
||||
maxSend = 100;
|
||||
else if (maxSend > 5000)
|
||||
maxSend = 5000;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.projectswg.forwarder.resources.networking;
|
||||
|
||||
public enum ClientServer {
|
||||
LOGIN,
|
||||
ZONE
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.projectswg.forwarder.resources.networking;
|
||||
|
||||
import com.projectswg.common.data.encodables.galaxy.Galaxy;
|
||||
import com.projectswg.common.network.NetBuffer;
|
||||
import com.projectswg.common.network.packets.PacketType;
|
||||
import com.projectswg.common.network.packets.swg.login.LoginClientId;
|
||||
import com.projectswg.common.network.packets.swg.login.LoginClusterStatus;
|
||||
import com.projectswg.forwarder.Forwarder.ForwarderData;
|
||||
import me.joshlarson.jlcommon.log.Log;
|
||||
import me.joshlarson.jlcommon.utilities.ByteUtilities;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
public class NetInterceptor {
|
||||
|
||||
private final ForwarderData data;
|
||||
|
||||
public NetInterceptor(ForwarderData data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public byte[] interceptClient(byte[] data) {
|
||||
if (data.length < 6)
|
||||
return data;
|
||||
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
|
||||
PacketType type = PacketType.fromCrc(bb.getInt(2));
|
||||
switch (type) {
|
||||
case LOGIN_CLIENT_ID:
|
||||
return setAutoLogin(NetBuffer.wrap(bb));
|
||||
default:
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] interceptServer(byte[] data) {
|
||||
if (data.length < 6)
|
||||
return data;
|
||||
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
|
||||
PacketType type = PacketType.fromCrc(bb.getInt(2));
|
||||
switch (type) {
|
||||
case LOGIN_CLUSTER_STATUS:
|
||||
return getServerList(NetBuffer.wrap(bb));
|
||||
default:
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] setAutoLogin(NetBuffer data) {
|
||||
LoginClientId id = new LoginClientId(data);
|
||||
if (!id.getUsername().equals(this.data.getUsername()) || !id.getPassword().isEmpty())
|
||||
return data.array();
|
||||
id.setPassword(this.data.getPassword());
|
||||
return id.encode().array();
|
||||
}
|
||||
|
||||
private byte[] getServerList(NetBuffer data) {
|
||||
LoginClusterStatus cluster = new LoginClusterStatus();
|
||||
cluster.decode(data);
|
||||
for (Galaxy g : cluster.getGalaxies()) {
|
||||
g.setAddress("127.0.0.1");
|
||||
g.setZonePort(this.data.getZonePort());
|
||||
g.setPingPort(this.data.getZonePort());
|
||||
}
|
||||
return cluster.encode().array();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,23 +25,17 @@
|
||||
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
package com.projectswg.networking;
|
||||
package com.projectswg.forwarder.resources.networking;
|
||||
|
||||
import me.joshlarson.jlcommon.concurrency.Delay;
|
||||
import me.joshlarson.jlcommon.log.Log;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayDeque;
|
||||
import java.net.*;
|
||||
import java.util.Locale;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import com.projectswg.common.concurrency.Delay;
|
||||
import com.projectswg.common.debug.Assert;
|
||||
import com.projectswg.common.debug.Log;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* This class represents a UDP server that listens for packets and
|
||||
@@ -49,55 +43,49 @@ import com.projectswg.common.debug.Log;
|
||||
*/
|
||||
public class UDPServer {
|
||||
|
||||
private final Object waitingForPacket;
|
||||
private final byte [] dataBuffer;
|
||||
private final Queue <DatagramPacket> inbound;
|
||||
private final AtomicBoolean running;
|
||||
private final InetSocketAddress bindAddr;
|
||||
private final Consumer<DatagramPacket> callback;
|
||||
|
||||
private DatagramSocket socket;
|
||||
private UDPCallback callback;
|
||||
private Thread thread;
|
||||
|
||||
public UDPServer(InetSocketAddress bindAddr) throws SocketException {
|
||||
this(bindAddr, 1024);
|
||||
public UDPServer(@Nonnull InetSocketAddress bindAddr, @Nonnull Consumer<DatagramPacket> callback) {
|
||||
this(bindAddr, 1024, callback);
|
||||
}
|
||||
|
||||
public UDPServer(InetSocketAddress bindAddr, int packetSize) {
|
||||
this.waitingForPacket = new Object();
|
||||
public UDPServer(@Nonnull InetSocketAddress bindAddr, int packetSize, @Nonnull Consumer<DatagramPacket> callback) {
|
||||
this.dataBuffer = new byte[packetSize];
|
||||
this.inbound = new ArrayDeque<>();
|
||||
this.running = new AtomicBoolean(false);
|
||||
this.bindAddr = bindAddr;
|
||||
this.callback = null;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
public void bind() throws SocketException {
|
||||
Assert.isNull(socket);
|
||||
bind(null);
|
||||
}
|
||||
|
||||
public void bind(Consumer<DatagramSocket> customizationCallback) throws SocketException {
|
||||
assert socket == null : "binding twice";
|
||||
socket = new DatagramSocket(bindAddr);
|
||||
if (customizationCallback != null)
|
||||
customizationCallback.accept(socket);
|
||||
start();
|
||||
}
|
||||
|
||||
public void close() {
|
||||
Assert.notNull(socket);
|
||||
assert socket != null : "socket already closed";
|
||||
stop();
|
||||
socket.close();
|
||||
socket = null;
|
||||
}
|
||||
|
||||
public DatagramPacket receive() {
|
||||
return inbound.poll();
|
||||
}
|
||||
|
||||
public int packetCount() {
|
||||
return inbound.size();
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
int port = socket.getLocalPort();
|
||||
while (port == 0) {
|
||||
port = socket.getLocalPort();
|
||||
if (Delay.sleepMilli(5))
|
||||
if (!Delay.sleepMilli(5))
|
||||
break;
|
||||
}
|
||||
return port;
|
||||
@@ -107,20 +95,18 @@ public class UDPServer {
|
||||
return running.get();
|
||||
}
|
||||
|
||||
public void waitForPacket() {
|
||||
synchronized (waitingForPacket) {
|
||||
try {
|
||||
while (inbound.size() == 0) {
|
||||
waitingForPacket.wait();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
|
||||
public boolean send(DatagramPacket packet) {
|
||||
try {
|
||||
socket.send(packet);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
String msg = e.getMessage();
|
||||
if (msg == null || !msg.toLowerCase(Locale.US).contains("socket closed")) {
|
||||
Log.e(e);
|
||||
close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean send(DatagramPacket packet) {
|
||||
return sendRaw(packet);
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean send(int port, InetAddress addr, byte [] data) {
|
||||
@@ -137,37 +123,41 @@ public class UDPServer {
|
||||
}
|
||||
|
||||
public boolean send(InetSocketAddress addr, byte [] data) {
|
||||
return send(addr.getPort(), addr.getAddress(), data);
|
||||
}
|
||||
|
||||
public void setCallback(UDPCallback callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
public void removeCallback() {
|
||||
callback = null;
|
||||
}
|
||||
|
||||
public interface UDPCallback {
|
||||
public void onReceivedPacket(DatagramPacket packet);
|
||||
return send(new DatagramPacket(data, data.length, addr));
|
||||
}
|
||||
|
||||
private void start() {
|
||||
Assert.test(!running.getAndSet(true));
|
||||
thread = new Thread(() -> run());
|
||||
running.set(true);
|
||||
thread = new Thread(this::run);
|
||||
thread.setName("UDPServer Port#" + getPort());
|
||||
thread.start();
|
||||
}
|
||||
|
||||
private void stop() {
|
||||
Assert.test(running.getAndSet(false));
|
||||
running.set(false);
|
||||
thread.interrupt();
|
||||
}
|
||||
|
||||
private void run() {
|
||||
try {
|
||||
while (running.get()) {
|
||||
loop();
|
||||
DatagramPacket packet = new DatagramPacket(dataBuffer, dataBuffer.length);
|
||||
try {
|
||||
socket.receive(packet);
|
||||
if (packet.getLength() > 0) {
|
||||
byte [] buffer = new byte[packet.getLength()];
|
||||
System.arraycopy(packet.getData(), 0, buffer, 0, packet.getLength());
|
||||
packet.setData(buffer);
|
||||
callback.accept(packet);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
String msg = e.getMessage();
|
||||
if (msg == null || !msg.toLowerCase(Locale.US).contains("socket closed")) {
|
||||
Log.e(e);
|
||||
close();
|
||||
}
|
||||
packet.setLength(0);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(e);
|
||||
@@ -176,53 +166,4 @@ public class UDPServer {
|
||||
}
|
||||
}
|
||||
|
||||
private void loop() {
|
||||
DatagramPacket packet = receiveRaw();
|
||||
if (packet.getLength() <= 0)
|
||||
return;
|
||||
if (callback != null)
|
||||
callback.onReceivedPacket(packet);
|
||||
else
|
||||
inbound.add(packet);
|
||||
notifyPacketReceived();
|
||||
}
|
||||
|
||||
private void notifyPacketReceived() {
|
||||
synchronized (waitingForPacket) {
|
||||
waitingForPacket.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean sendRaw(DatagramPacket packet) {
|
||||
try {
|
||||
socket.send(packet);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
String msg = e.getMessage();
|
||||
if (msg == null || !msg.toLowerCase(Locale.US).contains("socket closed")) {
|
||||
Log.e(e);
|
||||
close();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private DatagramPacket receiveRaw() {
|
||||
DatagramPacket packet = new DatagramPacket(dataBuffer, dataBuffer.length);
|
||||
try {
|
||||
socket.receive(packet);
|
||||
byte [] buffer = new byte[packet.getLength()];
|
||||
System.arraycopy(packet.getData(), 0, buffer, 0, packet.getLength());
|
||||
packet.setData(buffer);
|
||||
} catch (IOException e) {
|
||||
String msg = e.getMessage();
|
||||
if (msg == null || !msg.toLowerCase(Locale.US).contains("socket closed")) {
|
||||
Log.e(e);
|
||||
close();
|
||||
}
|
||||
packet.setLength(0);
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
package com.projectswg.networking.client.receiver;
|
||||
package com.projectswg.forwarder.resources.networking.data;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.projectswg.networking.Packet;
|
||||
import com.projectswg.networking.soe.Fragmented;
|
||||
import com.projectswg.forwarder.resources.networking.packets.Fragmented;
|
||||
import com.projectswg.forwarder.resources.networking.packets.Packet;
|
||||
|
||||
public class ClientFragmentedProcessor {
|
||||
public class FragmentedProcessor {
|
||||
|
||||
private final List<Fragmented> fragmentedBuffer;
|
||||
|
||||
public ClientFragmentedProcessor() {
|
||||
public FragmentedProcessor() {
|
||||
this.fragmentedBuffer = new ArrayList<>();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.projectswg.forwarder.resources.networking.data;
|
||||
|
||||
import com.projectswg.forwarder.resources.networking.packets.DataChannel;
|
||||
import com.projectswg.forwarder.resources.networking.packets.Fragmented;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class Packager {
|
||||
|
||||
private final AtomicInteger size;
|
||||
private final DataChannel channel;
|
||||
private final Queue<byte[]> outboundRaw;
|
||||
private final Queue<SequencedOutbound> outboundPackaged;
|
||||
private final ProtocolStack stack;
|
||||
|
||||
public Packager(Queue<byte[]> outboundRaw, Queue<SequencedOutbound> outboundPackaged, ProtocolStack stack) {
|
||||
this.size = new AtomicInteger(8);
|
||||
this.channel = new DataChannel();
|
||||
this.outboundRaw = outboundRaw;
|
||||
this.outboundPackaged = outboundPackaged;
|
||||
this.stack = stack;
|
||||
}
|
||||
|
||||
public void handle(int maxPackaged) {
|
||||
byte [] packet;
|
||||
int packetSize;
|
||||
|
||||
while (!outboundRaw.isEmpty() && outboundPackaged.size() < maxPackaged) {
|
||||
packet = outboundRaw.poll();
|
||||
if (packet == null)
|
||||
break;
|
||||
packetSize = getPacketLength(packet);
|
||||
|
||||
if (size.get() + packetSize >= 496) // overflowed previous packet
|
||||
sendDataChannel();
|
||||
|
||||
if (packetSize < 496) {
|
||||
addToDataChannel(packet, packetSize);
|
||||
} else {
|
||||
sendFragmented(packet);
|
||||
}
|
||||
}
|
||||
sendDataChannel();
|
||||
}
|
||||
|
||||
private void addToDataChannel(byte [] packet, int packetSize) {
|
||||
channel.addPacket(packet);
|
||||
size.getAndAdd(packetSize);
|
||||
}
|
||||
|
||||
private void sendDataChannel() {
|
||||
if (channel.getPacketCount() == 0)
|
||||
return;
|
||||
|
||||
channel.setSequence(stack.getAndIncrementTxSequence());
|
||||
outboundPackaged.add(new SequencedOutbound(channel.getSequence(), channel.encode().array()));
|
||||
reset();
|
||||
}
|
||||
|
||||
private void sendFragmented(byte [] packet) {
|
||||
Fragmented[] frags = Fragmented.encode(ByteBuffer.wrap(packet), stack.getTxSequence());
|
||||
stack.getAndIncrementTxSequence(frags.length);
|
||||
for (Fragmented frag : frags) {
|
||||
outboundPackaged.add(new SequencedOutbound(frag.getSequence(), frag.encode().array()));
|
||||
}
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
channel.clearPackets();
|
||||
size.set(8);
|
||||
}
|
||||
|
||||
private static int getPacketLength(byte [] data) {
|
||||
int len = data.length;
|
||||
if (len >= 255)
|
||||
return len + 3;
|
||||
return len + 1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package com.projectswg.forwarder.resources.networking.data;
|
||||
|
||||
import com.projectswg.forwarder.resources.networking.ClientServer;
|
||||
import com.projectswg.forwarder.resources.networking.packets.Fragmented;
|
||||
import com.projectswg.forwarder.resources.networking.packets.Packet;
|
||||
import com.projectswg.forwarder.resources.networking.packets.SequencedPacket;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.*;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
public class ProtocolStack {
|
||||
|
||||
private final PriorityQueue<SequencedPacket> sequenced;
|
||||
private final FragmentedProcessor fragmentedProcessor;
|
||||
private final InetSocketAddress source;
|
||||
private final BiConsumer<InetSocketAddress, byte[]> sender;
|
||||
private final ClientServer server;
|
||||
private final Queue<byte []> outboundRaw;
|
||||
private final Queue<SequencedOutbound> outboundPackaged;
|
||||
private final Packager packager;
|
||||
private final Object txMutex;
|
||||
|
||||
private InetSocketAddress pingSource;
|
||||
private int connectionId;
|
||||
private short rxSequence;
|
||||
private short txSequence;
|
||||
private boolean txOverflow;
|
||||
|
||||
public ProtocolStack(InetSocketAddress source, ClientServer server, BiConsumer<InetSocketAddress, byte[]> sender) {
|
||||
this.sequenced = new PriorityQueue<>();
|
||||
this.fragmentedProcessor = new FragmentedProcessor();
|
||||
this.source = source;
|
||||
this.sender = sender;
|
||||
this.server = server;
|
||||
this.outboundRaw = new LinkedList<>();
|
||||
this.outboundPackaged = new LinkedList<>();
|
||||
this.packager = new Packager(outboundRaw, outboundPackaged, this);
|
||||
this.txMutex = new Object();
|
||||
|
||||
this.connectionId = 0;
|
||||
this.rxSequence = 0;
|
||||
this.txSequence = 0;
|
||||
this.txOverflow = false;
|
||||
}
|
||||
|
||||
public void send(Packet packet) {
|
||||
send(packet.encode().array());
|
||||
}
|
||||
|
||||
public void send(byte [] data) {
|
||||
sender.accept(source, data);
|
||||
}
|
||||
|
||||
public void sendPing(byte [] data) {
|
||||
InetSocketAddress pingSource = this.pingSource;
|
||||
if (pingSource != null)
|
||||
sender.accept(pingSource, data);
|
||||
}
|
||||
|
||||
public InetSocketAddress getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
public ClientServer getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
public int getConnectionId() {
|
||||
return connectionId;
|
||||
}
|
||||
|
||||
public short getRxSequence() {
|
||||
return rxSequence;
|
||||
}
|
||||
|
||||
public short getTxSequence() {
|
||||
return txSequence;
|
||||
}
|
||||
|
||||
public void setPingSource(InetSocketAddress source) {
|
||||
this.pingSource = source;
|
||||
}
|
||||
|
||||
public void setConnectionId(int connectionId) {
|
||||
this.connectionId = connectionId;
|
||||
}
|
||||
|
||||
public short getAndIncrementTxSequence() {
|
||||
return getAndIncrementTxSequence(1);
|
||||
}
|
||||
|
||||
public short getAndIncrementTxSequence(int amount) {
|
||||
synchronized (txMutex) {
|
||||
short prev = this.txSequence;
|
||||
short next = prev;
|
||||
next += amount;
|
||||
if (prev > next)
|
||||
txOverflow = true;
|
||||
this.txSequence = next;
|
||||
return prev;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean addIncoming(@Nonnull SequencedPacket packet) {
|
||||
synchronized (sequenced) {
|
||||
if (packet.getSequence() < rxSequence)
|
||||
return true;
|
||||
// If it already exists in here, don't add it again
|
||||
for (SequencedPacket seq : sequenced) {
|
||||
if (seq.getSequence() == packet.getSequence())
|
||||
return true;
|
||||
}
|
||||
sequenced.add(packet);
|
||||
packet = sequenced.peek();
|
||||
assert packet != null : "the world is on fire";
|
||||
return packet.getSequence() == rxSequence;
|
||||
}
|
||||
}
|
||||
|
||||
public SequencedPacket getNextIncoming() {
|
||||
synchronized (sequenced) {
|
||||
SequencedPacket peek = sequenced.peek();
|
||||
if (peek == null || peek.getSequence() != rxSequence)
|
||||
return null;
|
||||
rxSequence++;
|
||||
return sequenced.poll();
|
||||
}
|
||||
}
|
||||
|
||||
public byte [] addFragmented(Fragmented frag) {
|
||||
return fragmentedProcessor.addFragmented(frag);
|
||||
}
|
||||
|
||||
public void addOutbound(@Nonnull byte [] data) {
|
||||
outboundRaw.offer(data);
|
||||
}
|
||||
|
||||
public short getFirstUnacknowledgedOutbound() {
|
||||
SequencedOutbound out = outboundPackaged.peek();
|
||||
if (out == null)
|
||||
return -1;
|
||||
return out.getSequence();
|
||||
}
|
||||
|
||||
public void clearAcknowledgedOutbound(short sequence) {
|
||||
synchronized (txMutex) {
|
||||
if (txOverflow && sequence <= txSequence) {
|
||||
outboundPackaged.removeIf(out -> out.getSequence() > txSequence);
|
||||
txOverflow = false;
|
||||
}
|
||||
SequencedOutbound out = outboundPackaged.peek();
|
||||
while (out != null && out.getSequence() <= sequence) {
|
||||
outboundPackaged.poll();
|
||||
out = outboundPackaged.peek();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void fillOutboundPackagedBuffer(int maxPackaged) {
|
||||
packager.handle(maxPackaged);
|
||||
}
|
||||
|
||||
public Collection<SequencedOutbound> getOutboundPackagedBuffer() {
|
||||
return Collections.unmodifiableCollection(outboundPackaged);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.projectswg.forwarder.resources.networking.data;
|
||||
|
||||
public class SequencedOutbound {
|
||||
|
||||
private final short sequence;
|
||||
private final byte [] data;
|
||||
private boolean sent;
|
||||
|
||||
public SequencedOutbound(short sequence, byte [] data) {
|
||||
this.sequence = sequence;
|
||||
this.data = data;
|
||||
this.sent = false;
|
||||
}
|
||||
|
||||
public short getSequence() {
|
||||
return sequence;
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public boolean isSent() {
|
||||
return sent;
|
||||
}
|
||||
|
||||
public void setSent(boolean sent) {
|
||||
this.sent = sent;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.projectswg.networking.encryption;
|
||||
package com.projectswg.forwarder.resources.networking.encryption;
|
||||
|
||||
import net.jpountz.lz4.LZ4Compressor;
|
||||
import net.jpountz.lz4.LZ4Factory;
|
||||
@@ -25,14 +25,15 @@
|
||||
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
package com.projectswg.networking.encryption;
|
||||
package com.projectswg.forwarder.resources.networking.encryption;
|
||||
|
||||
import me.joshlarson.jlcommon.log.Log;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.zip.DataFormatException;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.Inflater;
|
||||
|
||||
import com.projectswg.common.debug.Log;
|
||||
|
||||
public class Encryption {
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (c) 2015 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
|
||||
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
|
||||
* Our goal is to create an emulator which will provide a server for players to *
|
||||
* continue playing a game similar to the one they used to play. We are basing *
|
||||
* it on the final publish of the game prior to end-game events. *
|
||||
* *
|
||||
* This file is part of Holocore. *
|
||||
* *
|
||||
* -------------------------------------------------------------------------------- *
|
||||
* *
|
||||
* Holocore is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as *
|
||||
* published by the Free Software Foundation, either version 3 of the *
|
||||
* License, or (at your option) any later version. *
|
||||
* *
|
||||
* Holocore 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 Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
package com.projectswg.networking.encryption;
|
||||
/***********************************************************************************
|
||||
* Copyright (c) 2015 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
|
||||
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
|
||||
* Our goal is to create an emulator which will provide a server for players to *
|
||||
* continue playing a game similar to the one they used to play. We are basing *
|
||||
* it on the final publish of the game prior to end-game events. *
|
||||
* *
|
||||
* This file is part of Holocore. *
|
||||
* *
|
||||
* -------------------------------------------------------------------------------- *
|
||||
* *
|
||||
* Holocore is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as *
|
||||
* published by the Free Software Foundation, either version 3 of the *
|
||||
* License, or (at your option) any later version. *
|
||||
* *
|
||||
* Holocore 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 Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
package com.projectswg.forwarder.resources.networking.encryption;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
@@ -84,7 +84,7 @@ class EncryptionCRC {
|
||||
short gen = generate(data, 0, data.length-2, crcSeed);
|
||||
short crc = (short)((data[data.length-2] << 8) + (data[data.length-1] & 0xff));
|
||||
return crc == gen;
|
||||
} catch (java.lang.ArrayIndexOutOfBoundsException e) {
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
|
||||
}
|
||||
return false;
|
||||
@@ -25,13 +25,14 @@
|
||||
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
package com.projectswg.networking.encryption;
|
||||
package com.projectswg.forwarder.resources.networking.encryption;
|
||||
|
||||
import me.joshlarson.jlcommon.log.Log;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import com.projectswg.common.debug.Log;
|
||||
|
||||
public class MD5 {
|
||||
|
||||
@@ -41,9 +42,7 @@ public class MD5 {
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
md.update(text.getBytes("iso-8859-1"), 0, text.length());
|
||||
result = convertToHex(md.digest());
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.e(e);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
|
||||
Log.e(e);
|
||||
}
|
||||
return result;
|
||||
@@ -56,4 +55,4 @@ public class MD5 {
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,13 +25,10 @@
|
||||
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
package com.projectswg.networking.soe;
|
||||
package com.projectswg.forwarder.resources.networking.packets;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import com.projectswg.common.debug.Assert;
|
||||
import com.projectswg.networking.Packet;
|
||||
|
||||
public class Acknowledge extends Packet {
|
||||
|
||||
private short sequence;
|
||||
@@ -49,7 +46,7 @@ public class Acknowledge extends Packet {
|
||||
}
|
||||
|
||||
public void decode(ByteBuffer data) {
|
||||
Assert.test(data.array().length == 4);
|
||||
assert data.array().length == 4;
|
||||
data.position(2);
|
||||
sequence = getNetShort(data);
|
||||
}
|
||||
@@ -25,13 +25,10 @@
|
||||
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
package com.projectswg.networking.soe;
|
||||
package com.projectswg.forwarder.resources.networking.packets;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import com.projectswg.networking.Packet;
|
||||
|
||||
|
||||
public class ClientNetworkStatusUpdate extends Packet {
|
||||
|
||||
private int clientTickCount;
|
||||
@@ -25,14 +25,13 @@
|
||||
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
package com.projectswg.networking.soe;
|
||||
package com.projectswg.forwarder.resources.networking.packets;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.projectswg.common.network.NetBuffer;
|
||||
import com.projectswg.networking.Packet;
|
||||
|
||||
public class DataChannel extends Packet implements SequencedPacket {
|
||||
|
||||
@@ -25,13 +25,10 @@
|
||||
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
package com.projectswg.networking.soe;
|
||||
package com.projectswg.forwarder.resources.networking.packets;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import com.projectswg.networking.Packet;
|
||||
|
||||
|
||||
public class Disconnect extends Packet {
|
||||
|
||||
private int connectionId;
|
||||
@@ -25,13 +25,10 @@
|
||||
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
package com.projectswg.networking.soe;
|
||||
package com.projectswg.forwarder.resources.networking.packets;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import com.projectswg.networking.Packet;
|
||||
|
||||
|
||||
public class Fragmented extends Packet implements SequencedPacket {
|
||||
|
||||
private short sequence;
|
||||
@@ -1,9 +1,7 @@
|
||||
package com.projectswg.networking.soe;
|
||||
package com.projectswg.forwarder.resources.networking.packets;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import com.projectswg.networking.Packet;
|
||||
|
||||
public class KeepAlive extends Packet {
|
||||
|
||||
public KeepAlive() {
|
||||
@@ -25,15 +25,13 @@
|
||||
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
package com.projectswg.networking.soe;
|
||||
package com.projectswg.forwarder.resources.networking.packets;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.projectswg.common.network.NetBuffer;
|
||||
import com.projectswg.networking.Packet;
|
||||
|
||||
|
||||
public class MultiPacket extends Packet {
|
||||
|
||||
@@ -25,13 +25,10 @@
|
||||
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
package com.projectswg.networking.soe;
|
||||
package com.projectswg.forwarder.resources.networking.packets;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import com.projectswg.networking.Packet;
|
||||
|
||||
|
||||
public class OutOfOrder extends Packet {
|
||||
|
||||
private short sequence;
|
||||
@@ -25,7 +25,7 @@
|
||||
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
package com.projectswg.networking;
|
||||
package com.projectswg.forwarder.resources.networking.packets;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.projectswg.forwarder.resources.networking.packets;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class PingPacket extends Packet {
|
||||
|
||||
private byte [] payload;
|
||||
|
||||
public PingPacket(byte [] payload) {
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuffer data) {
|
||||
this.payload = data.array();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer encode() {
|
||||
return ByteBuffer.wrap(payload);
|
||||
}
|
||||
|
||||
public byte [] getPayload() {
|
||||
return payload;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,72 +25,17 @@
|
||||
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
package com.projectswg.utilities;
|
||||
package com.projectswg.forwarder.resources.networking.packets;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
|
||||
public class ByteUtilities {
|
||||
public class RawSWGPacket extends Packet {
|
||||
|
||||
private static final ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE);
|
||||
private static final char [] HEX = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
|
||||
private byte [] data;
|
||||
|
||||
public static String getHexString(byte [] bytes) {
|
||||
char [] data = new char[bytes.length*2+(bytes.length>0?bytes.length-1:0)];
|
||||
byte b;
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
b = bytes[i];
|
||||
data[i*3+0] = HEX[(b&0xFF) >>> 4];
|
||||
data[i*3+1] = HEX[b & 0x0F];
|
||||
if (i*3+2 < data.length)
|
||||
data[i*3+2] = ' ';
|
||||
}
|
||||
return new String(data);
|
||||
public RawSWGPacket(byte [] data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public static byte [] getHexStringArray(String string) {
|
||||
int len = string.length();
|
||||
if (len % 2 != 0)
|
||||
return new byte[0];
|
||||
byte[] data = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(string.charAt(i), 16) << 4) + Character.digit(string.charAt(i+1), 16));
|
||||
}
|
||||
public byte[] getRawData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public static byte [] longToBytes(long l) {
|
||||
byte [] b = new byte[Long.SIZE];
|
||||
synchronized (buffer) {
|
||||
buffer.putLong(0, l);
|
||||
System.arraycopy(buffer.array(), 0, b, 0, Long.SIZE);
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
public static long bytesToLong(byte [] a) {
|
||||
return bytesToLong(a, 0);
|
||||
}
|
||||
|
||||
public static long bytesToLong(byte [] a, int offset) {
|
||||
long l = 0;
|
||||
synchronized (buffer) {
|
||||
for (int i = 0; i < Long.SIZE; i++) {
|
||||
if (i < a.length)
|
||||
buffer.put(i, a[i+offset]);
|
||||
else
|
||||
buffer.put(i, (byte)0);
|
||||
}
|
||||
l = buffer.getLong(0);
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
public static String nextString(ByteBuffer data) {
|
||||
byte [] bData = data.array();
|
||||
StringBuilder str = new StringBuilder();
|
||||
for (int i = data.position(); i < bData.length && bData[i] >= ' ' && bData[i] <= '~'; i++)
|
||||
str.append((char) data.get());
|
||||
return str.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.projectswg.networking.soe;
|
||||
package com.projectswg.forwarder.resources.networking.packets;
|
||||
|
||||
public interface SequencedPacket extends Comparable<SequencedPacket> {
|
||||
short getSequence();
|
||||
@@ -1,9 +1,7 @@
|
||||
package com.projectswg.networking.soe;
|
||||
package com.projectswg.forwarder.resources.networking.packets;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import com.projectswg.networking.Packet;
|
||||
|
||||
public class SeriousErrorAcknowledge extends Packet {
|
||||
|
||||
public SeriousErrorAcknowledge() {
|
||||
@@ -1,9 +1,7 @@
|
||||
package com.projectswg.networking.soe;
|
||||
package com.projectswg.forwarder.resources.networking.packets;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import com.projectswg.networking.Packet;
|
||||
|
||||
public class SeriousErrorAcknowledgeReply extends Packet {
|
||||
|
||||
public SeriousErrorAcknowledgeReply() {
|
||||
@@ -25,12 +25,10 @@
|
||||
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
package com.projectswg.networking.soe;
|
||||
package com.projectswg.forwarder.resources.networking.packets;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import com.projectswg.networking.Packet;
|
||||
|
||||
|
||||
public class ServerNetworkStatusUpdate extends Packet {
|
||||
|
||||
@@ -25,13 +25,11 @@
|
||||
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
package com.projectswg.networking.soe;
|
||||
package com.projectswg.forwarder.resources.networking.packets;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import com.projectswg.networking.Packet;
|
||||
|
||||
|
||||
public class SessionRequest extends Packet {
|
||||
|
||||
@@ -25,14 +25,11 @@
|
||||
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
package com.projectswg.networking.soe;
|
||||
package com.projectswg.forwarder.resources.networking.packets;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import com.projectswg.networking.Packet;
|
||||
|
||||
|
||||
public class SessionResponse extends Packet {
|
||||
|
||||
private int connectionId;
|
||||
@@ -1,12 +1,6 @@
|
||||
package com.projectswg.recording;
|
||||
package com.projectswg.forwarder.resources.recording;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.*;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.OperatingSystemMXBean;
|
||||
import java.net.InetSocketAddress;
|
||||
@@ -15,18 +9,14 @@ import java.util.Map.Entry;
|
||||
import java.util.TimeZone;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import com.projectswg.common.debug.Assert;
|
||||
|
||||
public class PacketRecorder implements AutoCloseable, Closeable {
|
||||
|
||||
private static final byte VERSION = 1;
|
||||
private static final byte VERSION = 2;
|
||||
|
||||
private final DataOutputStream dataOut;
|
||||
private final OutputStream out;
|
||||
|
||||
public PacketRecorder(File file) throws FileNotFoundException {
|
||||
out = new FileOutputStream(file);
|
||||
dataOut = new DataOutputStream(out);
|
||||
dataOut = new DataOutputStream(new FileOutputStream(file));
|
||||
writeHeader();
|
||||
}
|
||||
|
||||
@@ -34,13 +24,11 @@ public class PacketRecorder implements AutoCloseable, Closeable {
|
||||
dataOut.close();
|
||||
}
|
||||
|
||||
public void record(boolean server, InetSocketAddress source, InetSocketAddress destination, byte [] data) {
|
||||
public void record(boolean server, byte [] data) {
|
||||
synchronized (dataOut) {
|
||||
try {
|
||||
dataOut.writeByte(server?1:0);
|
||||
dataOut.writeLong(System.currentTimeMillis());
|
||||
recordSocketAddress(source);
|
||||
recordSocketAddress(destination);
|
||||
dataOut.writeShort(data.length);
|
||||
dataOut.write(data);
|
||||
} catch (IOException e) {
|
||||
@@ -49,15 +37,6 @@ public class PacketRecorder implements AutoCloseable, Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
private void recordSocketAddress(InetSocketAddress addr) throws IOException {
|
||||
Assert.notNull(addr);
|
||||
Assert.test(!addr.isUnresolved());
|
||||
byte [] raw = addr.getAddress().getAddress();
|
||||
dataOut.writeByte(raw.length);
|
||||
dataOut.write(raw);
|
||||
dataOut.writeShort(addr.getPort());
|
||||
}
|
||||
|
||||
private void writeHeader() {
|
||||
try {
|
||||
dataOut.writeByte(VERSION);
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.projectswg.forwarder.services.client;
|
||||
|
||||
import me.joshlarson.jlcommon.control.Manager;
|
||||
import me.joshlarson.jlcommon.control.ManagerStructure;
|
||||
|
||||
@ManagerStructure(children = {
|
||||
ClientInboundDataService.class,
|
||||
ClientOutboundDataService.class,
|
||||
ClientProtocolService.class,
|
||||
ClientServerService.class
|
||||
})
|
||||
public class ClientConnectionManager extends Manager {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.projectswg.forwarder.services.client;
|
||||
|
||||
import com.projectswg.common.network.packets.PacketType;
|
||||
import com.projectswg.common.network.packets.swg.zone.HeartBeat;
|
||||
import com.projectswg.forwarder.intents.client.DataPacketInboundIntent;
|
||||
import com.projectswg.forwarder.intents.client.SonyPacketInboundIntent;
|
||||
import com.projectswg.forwarder.intents.client.UpdateStackIntent;
|
||||
import com.projectswg.forwarder.resources.networking.data.ProtocolStack;
|
||||
import com.projectswg.forwarder.resources.networking.packets.*;
|
||||
import me.joshlarson.jlcommon.control.IntentChain;
|
||||
import me.joshlarson.jlcommon.control.IntentHandler;
|
||||
import me.joshlarson.jlcommon.control.IntentMultiplexer;
|
||||
import me.joshlarson.jlcommon.control.IntentMultiplexer.Multiplexer;
|
||||
import me.joshlarson.jlcommon.control.Service;
|
||||
import me.joshlarson.jlcommon.log.Log;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public class ClientInboundDataService extends Service {
|
||||
|
||||
private final IntentMultiplexer multiplexer;
|
||||
private final AtomicReference<ProtocolStack> stack;
|
||||
private final IntentChain intentChain;
|
||||
|
||||
public ClientInboundDataService() {
|
||||
this.multiplexer = new IntentMultiplexer(this, ProtocolStack.class, Packet.class);
|
||||
this.stack = new AtomicReference<>(null);
|
||||
this.intentChain = new IntentChain();
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private void handleSonyPacketInboundIntent(SonyPacketInboundIntent spii) {
|
||||
ProtocolStack stack = this.stack.get();
|
||||
assert stack != null : "stack is null";
|
||||
multiplexer.call(stack, spii.getPacket());
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private void handleUpdateStackIntent(UpdateStackIntent sci) {
|
||||
stack.set(sci.getStack());
|
||||
}
|
||||
|
||||
@Multiplexer
|
||||
private void handlePingPacket(ProtocolStack stack, PingPacket ping) {
|
||||
onData(new HeartBeat(ping.getPayload()).encode().array());
|
||||
}
|
||||
|
||||
@Multiplexer
|
||||
private void handleRawSwgPacket(ProtocolStack stack, RawSWGPacket data) {
|
||||
onData(data.getRawData());
|
||||
}
|
||||
|
||||
@Multiplexer
|
||||
private void handleDataChannel(ProtocolStack stack, DataChannel data) {
|
||||
if (stack.addIncoming(data)) {
|
||||
readAvailablePackets(stack);
|
||||
} else {
|
||||
Log.d("Inbound Out of Order %d (data)", data.getSequence());
|
||||
for (short seq = stack.getRxSequence(); seq < data.getSequence(); seq++)
|
||||
stack.send(new OutOfOrder(seq));
|
||||
}
|
||||
}
|
||||
|
||||
@Multiplexer
|
||||
private void handleFragmented(ProtocolStack stack, Fragmented frag) {
|
||||
if (stack.addIncoming(frag)) {
|
||||
readAvailablePackets(stack);
|
||||
} else {
|
||||
Log.d("Inbound Out of Order %d (frag)", frag.getSequence());
|
||||
for (short seq = stack.getRxSequence(); seq < frag.getSequence(); seq++)
|
||||
stack.send(new OutOfOrder(seq));
|
||||
}
|
||||
}
|
||||
|
||||
private void readAvailablePackets(ProtocolStack stack) {
|
||||
short highestSequence = -1;
|
||||
SequencedPacket packet = stack.getNextIncoming();
|
||||
while (packet != null) {
|
||||
if (packet instanceof DataChannel) {
|
||||
for (byte [] data : ((DataChannel) packet).getPackets())
|
||||
onData(data);
|
||||
} else if (packet instanceof Fragmented) {
|
||||
byte [] data = stack.addFragmented((Fragmented) packet);
|
||||
if (data != null)
|
||||
onData(data);
|
||||
}
|
||||
Log.t("Data Inbound: %s", packet);
|
||||
highestSequence = packet.getSequence();
|
||||
packet = stack.getNextIncoming();
|
||||
}
|
||||
if (highestSequence != -1) {
|
||||
Log.t("Inbound Acknowledge %d", highestSequence);
|
||||
stack.send(new Acknowledge(highestSequence));
|
||||
}
|
||||
}
|
||||
|
||||
private void onData(byte [] data) {
|
||||
PacketType type = PacketType.fromCrc(ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN).getInt(2));
|
||||
Log.d("Incoming Data: %s", type);
|
||||
intentChain.broadcastAfter(getIntentManager(), new DataPacketInboundIntent(data));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package com.projectswg.forwarder.services.client;
|
||||
|
||||
import com.projectswg.common.network.NetBuffer;
|
||||
import com.projectswg.common.network.packets.PacketType;
|
||||
import com.projectswg.common.network.packets.swg.zone.HeartBeat;
|
||||
import com.projectswg.forwarder.intents.client.DataPacketInboundIntent;
|
||||
import com.projectswg.forwarder.intents.client.DataPacketOutboundIntent;
|
||||
import com.projectswg.forwarder.intents.client.SonyPacketInboundIntent;
|
||||
import com.projectswg.forwarder.intents.client.ClientConnectedIntent;
|
||||
import com.projectswg.forwarder.intents.client.ClientDisconnectedIntent;
|
||||
import com.projectswg.forwarder.intents.client.UpdateStackIntent;
|
||||
import com.projectswg.forwarder.resources.client.state.OutboundDataTuner;
|
||||
import com.projectswg.forwarder.resources.networking.data.ProtocolStack;
|
||||
import com.projectswg.forwarder.resources.networking.data.SequencedOutbound;
|
||||
import com.projectswg.forwarder.resources.networking.packets.Acknowledge;
|
||||
import com.projectswg.forwarder.resources.networking.packets.OutOfOrder;
|
||||
import com.projectswg.forwarder.resources.networking.packets.Packet;
|
||||
import me.joshlarson.jlcommon.concurrency.Delay;
|
||||
import me.joshlarson.jlcommon.concurrency.ScheduledThreadPool;
|
||||
import me.joshlarson.jlcommon.control.IntentHandler;
|
||||
import me.joshlarson.jlcommon.control.IntentMultiplexer;
|
||||
import me.joshlarson.jlcommon.control.IntentMultiplexer.Multiplexer;
|
||||
import me.joshlarson.jlcommon.control.Service;
|
||||
import me.joshlarson.jlcommon.log.Log;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public class ClientOutboundDataService extends Service {
|
||||
|
||||
private static final int TIMER_DELAY = 20;
|
||||
|
||||
private final IntentMultiplexer multiplexer;
|
||||
private final AtomicReference<ProtocolStack> stack;
|
||||
private final ScheduledThreadPool timerThread;
|
||||
private final OutboundDataTuner tuner;
|
||||
private final Object outboundMutex;
|
||||
|
||||
public ClientOutboundDataService() {
|
||||
this.multiplexer = new IntentMultiplexer(this, ProtocolStack.class, Packet.class);
|
||||
this.stack = new AtomicReference<>(null);
|
||||
this.timerThread = new ScheduledThreadPool(2, 5, "outbound-sender-%d");
|
||||
this.tuner = new OutboundDataTuner();
|
||||
this.outboundMutex = new Object();
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private void handleSonyPacketInboundIntent(SonyPacketInboundIntent spii) {
|
||||
ProtocolStack stack = this.stack.get();
|
||||
assert stack != null : "stack is null";
|
||||
multiplexer.call(stack, spii.getPacket());
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private void handleClientConnectedIntent(ClientConnectedIntent cci) {
|
||||
if (timerThread.isRunning())
|
||||
return;
|
||||
timerThread.start();
|
||||
timerThread.executeWithFixedDelay(TIMER_DELAY, TIMER_DELAY, this::timerCallback);
|
||||
timerThread.executeWithFixedRate(0, 5000, this::clearSentBit);
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private void handleClientDisconnectedIntent(ClientDisconnectedIntent cdi) {
|
||||
if (!timerThread.isRunning())
|
||||
return;
|
||||
timerThread.stop();
|
||||
timerThread.awaitTermination(500);
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private void handleUpdateStackIntent(UpdateStackIntent sci) {
|
||||
stack.set(sci.getStack());
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private void handleDataPacketInboundIntent(DataPacketInboundIntent dpii) {
|
||||
ProtocolStack stack = this.stack.get();
|
||||
if (stack == null)
|
||||
return;
|
||||
PacketType type = PacketType.fromCrc(ByteBuffer.wrap(dpii.getData()).order(ByteOrder.LITTLE_ENDIAN).getInt(2));
|
||||
switch (type) {
|
||||
case LAG_REQUEST:
|
||||
tuner.markLag();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private void handleDataPacketOutboundIntent(DataPacketOutboundIntent dpoi) {
|
||||
ProtocolStack stack = this.stack.get();
|
||||
if (stack == null)
|
||||
return;
|
||||
PacketType type = PacketType.fromCrc(ByteBuffer.wrap(dpoi.getData()).order(ByteOrder.LITTLE_ENDIAN).getInt(2));
|
||||
switch (type) {
|
||||
case CMD_START_SCENE:
|
||||
tuner.markStart();
|
||||
break;
|
||||
case CMD_SCENE_READY:
|
||||
tuner.markEnd();
|
||||
break;
|
||||
case HEART_BEAT_MESSAGE: {
|
||||
HeartBeat heartbeat = new HeartBeat();
|
||||
heartbeat.decode(NetBuffer.wrap(dpoi.getData()));
|
||||
if (heartbeat.getPayload().length > 0) {
|
||||
stack.sendPing(heartbeat.getPayload());
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
synchronized (outboundMutex) {
|
||||
stack.addOutbound(dpoi.getData());
|
||||
}
|
||||
}
|
||||
|
||||
@Multiplexer
|
||||
private void handleAcknowledgement(ProtocolStack stack, Acknowledge ack) {
|
||||
Log.t("Acknowledged: %d. Min Sequence: %d", ack.getSequence(), stack.getFirstUnacknowledgedOutbound());
|
||||
tuner.markAck();
|
||||
synchronized (outboundMutex) {
|
||||
stack.clearAcknowledgedOutbound(ack.getSequence());
|
||||
for (SequencedOutbound outbound : stack.getOutboundPackagedBuffer()) {
|
||||
outbound.setSent(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Multiplexer
|
||||
private void handleOutOfOrder(ProtocolStack stack, OutOfOrder ooo) {
|
||||
tuner.markOOO();
|
||||
synchronized (outboundMutex) {
|
||||
for (SequencedOutbound outbound : stack.getOutboundPackagedBuffer()) {
|
||||
if (outbound.getSequence() > ooo.getSequence())
|
||||
break;
|
||||
outbound.setSent(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void clearSentBit() {
|
||||
ProtocolStack stack = this.stack.get();
|
||||
if (stack == null)
|
||||
return;
|
||||
synchronized (outboundMutex) {
|
||||
for (SequencedOutbound outbound : stack.getOutboundPackagedBuffer()) {
|
||||
outbound.setSent(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void timerCallback() {
|
||||
ProtocolStack stack = this.stack.get();
|
||||
if (stack == null)
|
||||
return;
|
||||
int maxSend = tuner.getMaxSend();
|
||||
synchronized (outboundMutex) {
|
||||
stack.fillOutboundPackagedBuffer(maxSend);
|
||||
int sent = 0;
|
||||
int runStart = Integer.MIN_VALUE;
|
||||
int runEnd = 0;
|
||||
for (SequencedOutbound outbound : stack.getOutboundPackagedBuffer()) {
|
||||
runEnd = outbound.getSequence();
|
||||
if (runStart == Integer.MIN_VALUE) {
|
||||
runStart = runEnd;
|
||||
}
|
||||
stack.send(outbound.getData());
|
||||
Delay.sleepMicro(50);
|
||||
if (sent++ >= maxSend)
|
||||
break;
|
||||
}
|
||||
if (runStart != Integer.MIN_VALUE)
|
||||
Log.t("Sending to %s: %d - %d", stack.getSource(), runStart, runEnd);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.projectswg.forwarder.services.client;
|
||||
|
||||
import com.projectswg.forwarder.intents.client.SonyPacketInboundIntent;
|
||||
import com.projectswg.forwarder.intents.client.UpdateStackIntent;
|
||||
import com.projectswg.forwarder.resources.networking.data.ProtocolStack;
|
||||
import com.projectswg.forwarder.resources.networking.packets.ClientNetworkStatusUpdate;
|
||||
import com.projectswg.forwarder.resources.networking.packets.KeepAlive;
|
||||
import com.projectswg.forwarder.resources.networking.packets.Packet;
|
||||
import com.projectswg.forwarder.resources.networking.packets.ServerNetworkStatusUpdate;
|
||||
import me.joshlarson.jlcommon.control.IntentHandler;
|
||||
import me.joshlarson.jlcommon.control.IntentMultiplexer;
|
||||
import me.joshlarson.jlcommon.control.IntentMultiplexer.Multiplexer;
|
||||
import me.joshlarson.jlcommon.control.Service;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public class ClientProtocolService extends Service {
|
||||
|
||||
private static final int GALACTIC_BASE_TIME = 1323043200;
|
||||
|
||||
private final AtomicReference<ProtocolStack> stack;
|
||||
private final IntentMultiplexer multiplexer;
|
||||
|
||||
public ClientProtocolService() {
|
||||
this.stack = new AtomicReference<>(null);
|
||||
this.multiplexer = new IntentMultiplexer(this, ProtocolStack.class, Packet.class);
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private void handleSonyPacketInboundIntent(SonyPacketInboundIntent spii) {
|
||||
ProtocolStack stack = this.stack.get();
|
||||
assert stack != null : "stack is null";
|
||||
multiplexer.call(stack, spii.getPacket());
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private void handleUpdateStackIntent(UpdateStackIntent sci) {
|
||||
stack.set(sci.getStack());
|
||||
}
|
||||
|
||||
@Multiplexer
|
||||
private void handleClientNetworkStatus(ProtocolStack stack, ClientNetworkStatusUpdate update) {
|
||||
ServerNetworkStatusUpdate serverNet = new ServerNetworkStatusUpdate();
|
||||
serverNet.setClientTickCount((short) update.getTick());
|
||||
serverNet.setServerSyncStampLong((int) (System.currentTimeMillis()-GALACTIC_BASE_TIME));
|
||||
serverNet.setClientPacketsSent(update.getSent());
|
||||
serverNet.setClientPacketsRecv(update.getRecv());
|
||||
serverNet.setServerPacketsSent(stack.getTxSequence());
|
||||
serverNet.setServerPacketsRecv(stack.getRxSequence());
|
||||
stack.send(serverNet);
|
||||
}
|
||||
|
||||
@Multiplexer
|
||||
private void handleKeepAlive(ProtocolStack stack, KeepAlive keepAlive) {
|
||||
stack.send(new KeepAlive());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
package com.projectswg.forwarder.services.client;
|
||||
|
||||
import com.projectswg.forwarder.Forwarder.ForwarderData;
|
||||
import com.projectswg.forwarder.intents.client.SonyPacketInboundIntent;
|
||||
import com.projectswg.forwarder.intents.client.ClientConnectedIntent;
|
||||
import com.projectswg.forwarder.intents.client.ClientDisconnectedIntent;
|
||||
import com.projectswg.forwarder.intents.client.UpdateStackIntent;
|
||||
import com.projectswg.forwarder.intents.control.StartForwarderIntent;
|
||||
import com.projectswg.forwarder.intents.control.StopForwarderIntent;
|
||||
import com.projectswg.forwarder.resources.networking.ClientServer;
|
||||
import com.projectswg.forwarder.resources.networking.UDPServer;
|
||||
import com.projectswg.forwarder.resources.networking.data.ProtocolStack;
|
||||
import com.projectswg.forwarder.resources.networking.packets.*;
|
||||
import com.projectswg.forwarder.resources.networking.packets.Disconnect.DisconnectReason;
|
||||
import me.joshlarson.jlcommon.control.IntentChain;
|
||||
import me.joshlarson.jlcommon.control.IntentHandler;
|
||||
import me.joshlarson.jlcommon.control.Service;
|
||||
import me.joshlarson.jlcommon.log.Log;
|
||||
import me.joshlarson.jlcommon.utilities.ByteUtilities;
|
||||
|
||||
import java.net.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public class ClientServerService extends Service {
|
||||
|
||||
private final IntentChain intentChain;
|
||||
private final AtomicReference<ProtocolStack> stack;
|
||||
|
||||
private ForwarderData data;
|
||||
private UDPServer loginServer;
|
||||
private UDPServer zoneServer;
|
||||
|
||||
public ClientServerService() {
|
||||
this.intentChain = new IntentChain();
|
||||
this.stack = new AtomicReference<>(null);
|
||||
this.data = null;
|
||||
this.loginServer = null;
|
||||
this.zoneServer = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOperational() {
|
||||
return data == null || (loginServer != null && zoneServer != null && loginServer.isRunning() && zoneServer.isRunning());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stop() {
|
||||
setStack(null);
|
||||
return true;
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private void handleStartForwarderIntent(StartForwarderIntent sfi) {
|
||||
try {
|
||||
ForwarderData data = sfi.getData();
|
||||
Log.t("Initializing login udp server...");
|
||||
loginServer = new UDPServer(new InetSocketAddress(InetAddress.getLoopbackAddress(), data.getLoginPort()), 496, this::onLoginPacket);
|
||||
Log.t("Initializing zone udp server...");
|
||||
zoneServer = new UDPServer(new InetSocketAddress(InetAddress.getLoopbackAddress(), data.getZonePort()), 496, this::onZonePacket);
|
||||
|
||||
Log.t("Binding to login server...");
|
||||
loginServer.bind(this::customizeUdpServer);
|
||||
Log.t("Binding to zone server...");
|
||||
zoneServer.bind(this::customizeUdpServer);
|
||||
|
||||
data.setLoginPort(loginServer.getPort());
|
||||
data.setZonePort(zoneServer.getPort());
|
||||
|
||||
Log.i("Initialized login (%d) and zone servers (%d)", loginServer.getPort(), zoneServer.getPort());
|
||||
} catch (SocketException e) {
|
||||
Log.a(e);
|
||||
if (loginServer != null)
|
||||
loginServer.close();
|
||||
if (zoneServer != null)
|
||||
zoneServer.close();
|
||||
loginServer = null;
|
||||
zoneServer = null;
|
||||
}
|
||||
data = sfi.getData();
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private void handleStopForwarderIntent(StopForwarderIntent sfi) {
|
||||
Log.t("Closing the login udp server...");
|
||||
if (loginServer != null)
|
||||
loginServer.close();
|
||||
Log.t("Closing the zone udp server...");
|
||||
if (zoneServer != null)
|
||||
zoneServer.close();
|
||||
loginServer = null;
|
||||
zoneServer = null;
|
||||
Log.i("Closed the login and zone udp servers");
|
||||
}
|
||||
|
||||
private void customizeUdpServer(DatagramSocket socket) {
|
||||
try {
|
||||
socket.setReuseAddress(false);
|
||||
socket.setTrafficClass(0x02 | 0x04 | 0x08 | 0x10);
|
||||
socket.setBroadcast(false);
|
||||
socket.setReceiveBufferSize(496 * 2048);
|
||||
socket.setSendBufferSize(496 * 2048);
|
||||
} catch (SocketException e) {
|
||||
Log.w(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void onLoginPacket(DatagramPacket packet) {
|
||||
process((InetSocketAddress) packet.getSocketAddress(), ClientServer.LOGIN, packet.getData());
|
||||
}
|
||||
|
||||
private void onZonePacket(DatagramPacket packet) {
|
||||
process((InetSocketAddress) packet.getSocketAddress(), ClientServer.ZONE, packet.getData());
|
||||
}
|
||||
|
||||
private void send(InetSocketAddress addr, ClientServer server, byte [] data) {
|
||||
switch (server) {
|
||||
case LOGIN:
|
||||
loginServer.send(addr, data);
|
||||
break;
|
||||
case ZONE:
|
||||
zoneServer.send(addr, data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void process(InetSocketAddress source, ClientServer server, byte [] data) {
|
||||
Packet parsed = parse(data);
|
||||
if (parsed == null)
|
||||
return;
|
||||
if (parsed instanceof MultiPacket) {
|
||||
for (byte [] child : ((MultiPacket) parsed).getPackets()) {
|
||||
process(source, server, child);
|
||||
}
|
||||
} else {
|
||||
broadcast(source, server, parsed);
|
||||
}
|
||||
}
|
||||
|
||||
private void broadcast(InetSocketAddress source, ClientServer server, Packet parsed) {
|
||||
ProtocolStack stack = this.stack.get();
|
||||
if (parsed instanceof SessionRequest) {
|
||||
stack = new ProtocolStack(source, server, (remote, data) -> send(remote, server, data));
|
||||
stack.setConnectionId(((SessionRequest) parsed).getConnectionId());
|
||||
setStack(stack);
|
||||
stack.send(new SessionResponse(((SessionRequest) parsed).getConnectionId(), 0, (byte) 0, (byte) 0, (byte) 0, 496));
|
||||
}
|
||||
if (stack != null && parsed instanceof PingPacket) {
|
||||
stack.setPingSource(source);
|
||||
} else if (stack == null || !stack.getSource().equals(source) || stack.getServer() != server) {
|
||||
Log.t("[%s]@%s DROPPED %s", source, server, parsed);
|
||||
return;
|
||||
}
|
||||
Log.t("[%s]@%s sent: %s", source, server, parsed);
|
||||
intentChain.broadcastAfter(getIntentManager(), new SonyPacketInboundIntent(parsed));
|
||||
if (parsed instanceof Disconnect) {
|
||||
setStack(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void setStack(ProtocolStack stack) {
|
||||
ProtocolStack oldStack = this.stack.getAndSet(stack);
|
||||
if (oldStack != null) {
|
||||
oldStack.send(new Disconnect(oldStack.getConnectionId(), DisconnectReason.MANAGER_DELETED));
|
||||
if (stack == null)
|
||||
intentChain.broadcastAfter(getIntentManager(), new ClientDisconnectedIntent());
|
||||
}
|
||||
if (stack != null && stack.getServer() == ClientServer.LOGIN) {
|
||||
intentChain.broadcastAfter(getIntentManager(), new ClientConnectedIntent());
|
||||
}
|
||||
intentChain.broadcastAfter(getIntentManager(), new UpdateStackIntent(stack));
|
||||
}
|
||||
|
||||
private static Packet parse(byte [] rawData) {
|
||||
if (rawData.length < 4)
|
||||
return null;
|
||||
|
||||
ByteBuffer data = ByteBuffer.wrap(rawData);
|
||||
short opcode = data.getShort(0);
|
||||
switch (opcode) {
|
||||
case 0x01: return new SessionRequest(data);
|
||||
case 0x03: return new MultiPacket(data);
|
||||
case 0x05: return new Disconnect(data);
|
||||
case 0x06: return new KeepAlive(data);
|
||||
case 0x07: return new ClientNetworkStatusUpdate(data);
|
||||
case 0x09:
|
||||
case 0x0A:
|
||||
case 0x0B:
|
||||
case 0x0C: return new DataChannel(data);
|
||||
case 0x0D:
|
||||
case 0x0E:
|
||||
case 0x0F:
|
||||
case 0x10: return new Fragmented(data);
|
||||
case 0x11:
|
||||
case 0x12:
|
||||
case 0x13:
|
||||
case 0x14: return new OutOfOrder(data);
|
||||
case 0x15:
|
||||
case 0x16:
|
||||
case 0x17:
|
||||
case 0x18: return new Acknowledge(data);
|
||||
default:
|
||||
if (rawData.length == 4)
|
||||
return new PingPacket(rawData);
|
||||
if (rawData.length >= 6)
|
||||
return new RawSWGPacket(rawData);
|
||||
Log.w("Unknown SOE packet: %d %s", opcode, ByteUtilities.getHexString(data.array()));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.projectswg.forwarder.services.crash;
|
||||
|
||||
import me.joshlarson.jlcommon.control.Manager;
|
||||
import me.joshlarson.jlcommon.control.ManagerStructure;
|
||||
|
||||
@ManagerStructure(children = {
|
||||
PacketRecordingService.class,
|
||||
IntentRecordingService.class
|
||||
})
|
||||
public class CrashManager extends Manager {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package com.projectswg.forwarder.services.crash;
|
||||
|
||||
import com.projectswg.forwarder.Forwarder.ForwarderData;
|
||||
import com.projectswg.forwarder.intents.client.ClientConnectedIntent;
|
||||
import com.projectswg.forwarder.intents.client.ClientDisconnectedIntent;
|
||||
import com.projectswg.forwarder.intents.client.UpdateStackIntent;
|
||||
import com.projectswg.forwarder.intents.control.ClientCrashedIntent;
|
||||
import com.projectswg.forwarder.intents.control.StartForwarderIntent;
|
||||
import com.projectswg.forwarder.intents.control.StopForwarderIntent;
|
||||
import com.projectswg.forwarder.intents.server.ServerConnectedIntent;
|
||||
import com.projectswg.forwarder.intents.server.ServerDisconnectedIntent;
|
||||
import com.projectswg.forwarder.resources.networking.data.ProtocolStack;
|
||||
import me.joshlarson.jlcommon.control.Intent;
|
||||
import me.joshlarson.jlcommon.control.IntentHandler;
|
||||
import me.joshlarson.jlcommon.control.Service;
|
||||
import me.joshlarson.jlcommon.log.Log;
|
||||
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
public class IntentRecordingService extends Service {
|
||||
|
||||
private final DateTimeFormatter dateTimeFormatter;
|
||||
|
||||
private Path logPath;
|
||||
private FileWriter logWriter;
|
||||
private ForwarderData data;
|
||||
|
||||
public IntentRecordingService() {
|
||||
this.dateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-yy HH:mm:ss.SSS zzz").withZone(ZoneId.systemDefault());
|
||||
this.logPath = null;
|
||||
this.logWriter = null;
|
||||
this.data = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean initialize() {
|
||||
try {
|
||||
logPath = Files.createTempFile("HolocoreIntents", ".txt");
|
||||
logWriter = new FileWriter(logPath.toFile());
|
||||
} catch (IOException e) {
|
||||
Log.a(e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean terminate() {
|
||||
try {
|
||||
if (logWriter != null)
|
||||
logWriter.close();
|
||||
if (logPath != null)
|
||||
return logPath.toFile().delete();
|
||||
} catch (IOException e) {
|
||||
Log.w(e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private void handleClientConnectedIntent(ClientConnectedIntent cci) {
|
||||
ForwarderData data = this.data;
|
||||
if (data == null)
|
||||
log(cci, "");
|
||||
else
|
||||
log(cci, "Address='%s' Username='%s' Login='%d' Zone='%d'", data.getAddress(), data.getUsername(), data.getLoginPort(), data.getZonePort());
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private void handleClientDisconnectedIntent(ClientDisconnectedIntent cdi) {
|
||||
log(cdi, "");
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private void handleUpdateStackIntent(UpdateStackIntent usi) {
|
||||
ProtocolStack stack = usi.getStack();
|
||||
if (stack == null)
|
||||
log(usi, "Stack='null'");
|
||||
else
|
||||
log(usi, "Server='%s' Source='%s' ConnectionId='%d'", stack.getServer(), stack.getSource(), stack.getConnectionId());
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private void handleStartForwarderIntent(StartForwarderIntent sfi) {
|
||||
this.data = sfi.getData();
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private void handleStopForwarderIntent(StopForwarderIntent sfi) {
|
||||
ForwarderData data = this.data;
|
||||
if (data == null)
|
||||
log(sfi, "");
|
||||
else
|
||||
log(sfi, "Address='%s' Username='%s' Login='%d' Zone='%d'", data.getAddress(), data.getUsername(), data.getLoginPort(), data.getZonePort());
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private void handleServerConnectedIntent(ServerConnectedIntent sci) {
|
||||
ForwarderData data = this.data;
|
||||
if (data == null)
|
||||
log(sci, "");
|
||||
else
|
||||
log(sci, "Address='%s' Username='%s' Login='%d' Zone='%d'", data.getAddress(), data.getUsername(), data.getLoginPort(), data.getZonePort());
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private void handleServerDisconnectedIntent(ServerDisconnectedIntent sdi) {
|
||||
log(sdi, "");
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private void handleClientCrashedIntent(ClientCrashedIntent cci) {
|
||||
log(cci, "");
|
||||
try {
|
||||
logWriter.flush();
|
||||
byte[] data = Files.readAllBytes(logPath);
|
||||
ZipEntry entry = new ZipEntry("log.txt");
|
||||
entry.setTime(System.currentTimeMillis());
|
||||
entry.setSize(data.length);
|
||||
entry.setMethod(ZipOutputStream.DEFLATED);
|
||||
synchronized (cci.getFileMutex()) {
|
||||
cci.getOutputStream().putNextEntry(entry);
|
||||
cci.getOutputStream().write(data);
|
||||
cci.getOutputStream().closeEntry();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w("Failed to write intent data to crash log - IOException");
|
||||
Log.w(e);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void log(Intent i, String message, Object ... args) {
|
||||
try {
|
||||
logWriter.write(dateTimeFormatter.format(Instant.now()) + ": " + i.getClass().getSimpleName() + ' ' + String.format(message, args) + '\r' + '\n');
|
||||
} catch (IOException e) {
|
||||
Log.e("Failed to write to intent log");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.projectswg.forwarder.services.crash;
|
||||
|
||||
import com.projectswg.forwarder.intents.client.DataPacketInboundIntent;
|
||||
import com.projectswg.forwarder.intents.client.DataPacketOutboundIntent;
|
||||
import com.projectswg.forwarder.intents.control.ClientCrashedIntent;
|
||||
import com.projectswg.forwarder.resources.recording.PacketRecorder;
|
||||
import me.joshlarson.jlcommon.control.IntentHandler;
|
||||
import me.joshlarson.jlcommon.control.Service;
|
||||
import me.joshlarson.jlcommon.log.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
public class PacketRecordingService extends Service {
|
||||
|
||||
private Path recorderPath;
|
||||
private PacketRecorder recorder;
|
||||
|
||||
public PacketRecordingService() {
|
||||
this.recorder = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean initialize() {
|
||||
try {
|
||||
recorderPath = Files.createTempFile("HolocorePackets", ".hcap");
|
||||
recorder = new PacketRecorder(recorderPath.toFile());
|
||||
} catch (IOException e) {
|
||||
Log.a(e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean terminate() {
|
||||
try {
|
||||
if (recorder != null)
|
||||
recorder.close();
|
||||
return recorderPath.toFile().delete();
|
||||
} catch (IOException e) {
|
||||
Log.w(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private void handleClientCrashedIntent(ClientCrashedIntent cci) {
|
||||
try {
|
||||
byte[] data = Files.readAllBytes(recorderPath);
|
||||
ZipEntry entry = new ZipEntry("packet_log.hcap");
|
||||
entry.setTime(System.currentTimeMillis());
|
||||
entry.setSize(data.length);
|
||||
entry.setMethod(ZipOutputStream.DEFLATED);
|
||||
synchronized (cci.getFileMutex()) {
|
||||
cci.getOutputStream().putNextEntry(entry);
|
||||
cci.getOutputStream().write(data);
|
||||
cci.getOutputStream().closeEntry();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w("Failed to write packet data to crash log - IOException");
|
||||
Log.w(e);
|
||||
}
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private void handleDataPacketInboundIntent(DataPacketInboundIntent dpii) {
|
||||
recorder.record(false, dpii.getData());
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private void handleDataPacketOutboundIntent(DataPacketOutboundIntent dpoi) {
|
||||
recorder.record(true, dpoi.getData());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.projectswg.forwarder.services.server;
|
||||
|
||||
import me.joshlarson.jlcommon.control.Manager;
|
||||
import me.joshlarson.jlcommon.control.ManagerStructure;
|
||||
|
||||
@ManagerStructure(children = {
|
||||
ServerConnectionService.class
|
||||
})
|
||||
public class ServerConnectionManager extends Manager {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package com.projectswg.forwarder.services.server;
|
||||
|
||||
import com.projectswg.connection.HolocoreSocket;
|
||||
import com.projectswg.connection.ServerConnectionChangedReason;
|
||||
import com.projectswg.connection.packets.RawPacket;
|
||||
import com.projectswg.forwarder.Forwarder.ForwarderData;
|
||||
import com.projectswg.forwarder.intents.client.DataPacketInboundIntent;
|
||||
import com.projectswg.forwarder.intents.client.DataPacketOutboundIntent;
|
||||
import com.projectswg.forwarder.intents.client.ClientConnectedIntent;
|
||||
import com.projectswg.forwarder.intents.client.ClientDisconnectedIntent;
|
||||
import com.projectswg.forwarder.intents.control.StartForwarderIntent;
|
||||
import com.projectswg.forwarder.intents.control.StopForwarderIntent;
|
||||
import com.projectswg.forwarder.intents.server.ServerConnectedIntent;
|
||||
import com.projectswg.forwarder.intents.server.ServerDisconnectedIntent;
|
||||
import com.projectswg.forwarder.resources.networking.NetInterceptor;
|
||||
import me.joshlarson.jlcommon.concurrency.BasicThread;
|
||||
import me.joshlarson.jlcommon.concurrency.Delay;
|
||||
import me.joshlarson.jlcommon.control.IntentChain;
|
||||
import me.joshlarson.jlcommon.control.IntentHandler;
|
||||
import me.joshlarson.jlcommon.control.Service;
|
||||
import me.joshlarson.jlcommon.log.Log;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class ServerConnectionService extends Service {
|
||||
|
||||
private final IntentChain intentChain;
|
||||
private final AtomicBoolean running;
|
||||
private final BasicThread thread;
|
||||
|
||||
private HolocoreSocket holocore;
|
||||
private NetInterceptor interceptor;
|
||||
private ForwarderData data;
|
||||
|
||||
public ServerConnectionService() {
|
||||
this.intentChain = new IntentChain();
|
||||
this.running = new AtomicBoolean(false);
|
||||
this.thread = new BasicThread("server-connection", this::runningLoop);
|
||||
this.holocore = null;
|
||||
this.interceptor = null;
|
||||
this.data = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stop() {
|
||||
return stopRunningLoop();
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private void handleStartForwarderIntent(StartForwarderIntent sfi) {
|
||||
interceptor = new NetInterceptor(sfi.getData());
|
||||
data = sfi.getData();
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private void handleStopForwarderIntent(StopForwarderIntent sfi) {
|
||||
stopRunningLoop();
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private void handleClientConnectedIntent(ClientConnectedIntent cci) {
|
||||
stopRunningLoop();
|
||||
thread.start();
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private void handleClientDisconnectedIntent(ClientDisconnectedIntent cdi) {
|
||||
stopRunningLoop();
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private void handleDataPacketInboundIntent(DataPacketInboundIntent dpii) {
|
||||
if (running.get())
|
||||
holocore.send(interceptor.interceptClient(dpii.getData()));
|
||||
}
|
||||
|
||||
private boolean stopRunningLoop() {
|
||||
if (running.getAndSet(false)) {
|
||||
thread.stop(true);
|
||||
return thread.awaitTermination(500);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void runningLoop() {
|
||||
holocore = new HolocoreSocket(data.getAddress().getAddress(), data.getAddress().getPort());
|
||||
running.set(true);
|
||||
while (running.get()) {
|
||||
try {
|
||||
connectedLoop();
|
||||
} catch (Throwable t) {
|
||||
Log.w(t);
|
||||
Log.i("Disconnected from server. Sleeping 3 seconds");
|
||||
holocore.disconnect(ServerConnectionChangedReason.UNKNOWN);
|
||||
Delay.sleepMilli(3000);
|
||||
}
|
||||
}
|
||||
Log.t("Destroying holocore connection");
|
||||
holocore.terminate();
|
||||
holocore = null;
|
||||
}
|
||||
|
||||
private void connectedLoop() {
|
||||
Log.t("Attempting to connect to server at %s", holocore.getRemoteAddress());
|
||||
if (!holocore.connect(5000)) {
|
||||
Log.t("Failed to connect to server. Sleeping 3 seconds");
|
||||
Delay.sleepMilli(3000);
|
||||
return;
|
||||
}
|
||||
intentChain.broadcastAfter(getIntentManager(), new ServerConnectedIntent());
|
||||
Log.i("Successfully connected to server at %s", holocore.getRemoteAddress());
|
||||
while (holocore.isConnected()) {
|
||||
if (!running.get()) {
|
||||
holocore.disconnect(ServerConnectionChangedReason.CLIENT_DISCONNECT);
|
||||
break;
|
||||
}
|
||||
|
||||
RawPacket inbound = holocore.receive();
|
||||
if (inbound == null) {
|
||||
holocore.disconnect(ServerConnectionChangedReason.SOCKET_CLOSED);
|
||||
break;
|
||||
}
|
||||
intentChain.broadcastAfter(getIntentManager(), new DataPacketOutboundIntent(interceptor.interceptServer(inbound.getData())));
|
||||
}
|
||||
intentChain.broadcastAfter(getIntentManager(), new ServerDisconnectedIntent());
|
||||
Log.i("Disconnected from server");
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user