diff --git a/build.gradle b/build.gradle index 42a128d..d7fd73c 100644 --- a/build.gradle +++ b/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']) } diff --git a/src/com/projectswg/Connections.java b/src/com/projectswg/Connections.java deleted file mode 100644 index 98ea2cd..0000000 --- a/src/com/projectswg/Connections.java +++ /dev/null @@ -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); - } - -} diff --git a/src/com/projectswg/Forwarder.java b/src/com/projectswg/Forwarder.java deleted file mode 100644 index ceeb2fe..0000000 --- a/src/com/projectswg/Forwarder.java +++ /dev/null @@ -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"; - } - -} diff --git a/src/com/projectswg/HolocoreConnection.java b/src/com/projectswg/HolocoreConnection.java deleted file mode 100644 index f465070..0000000 --- a/src/com/projectswg/HolocoreConnection.java +++ /dev/null @@ -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()); - } - -} diff --git a/src/com/projectswg/intents/ClientConnectionChangedIntent.java b/src/com/projectswg/intents/ClientConnectionChangedIntent.java deleted file mode 100644 index 9ebd3f0..0000000 --- a/src/com/projectswg/intents/ClientConnectionChangedIntent.java +++ /dev/null @@ -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; - } - -} diff --git a/src/com/projectswg/intents/ClientSonyPacketIntent.java b/src/com/projectswg/intents/ClientSonyPacketIntent.java deleted file mode 100644 index b88ccb3..0000000 --- a/src/com/projectswg/intents/ClientSonyPacketIntent.java +++ /dev/null @@ -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; - } - -} diff --git a/src/com/projectswg/intents/ClientToServerPacketIntent.java b/src/com/projectswg/intents/ClientToServerPacketIntent.java deleted file mode 100644 index dd0a890..0000000 --- a/src/com/projectswg/intents/ClientToServerPacketIntent.java +++ /dev/null @@ -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; - } - -} diff --git a/src/com/projectswg/intents/ServerConnectionChangedIntent.java b/src/com/projectswg/intents/ServerConnectionChangedIntent.java deleted file mode 100644 index 73759f0..0000000 --- a/src/com/projectswg/intents/ServerConnectionChangedIntent.java +++ /dev/null @@ -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; - } - -} diff --git a/src/com/projectswg/intents/ServerToClientPacketIntent.java b/src/com/projectswg/intents/ServerToClientPacketIntent.java deleted file mode 100644 index 90323a8..0000000 --- a/src/com/projectswg/intents/ServerToClientPacketIntent.java +++ /dev/null @@ -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; - } - -} diff --git a/src/com/projectswg/networking/NetInterceptor.java b/src/com/projectswg/networking/NetInterceptor.java deleted file mode 100644 index 6c36778..0000000 --- a/src/com/projectswg/networking/NetInterceptor.java +++ /dev/null @@ -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; - } - - } - -} diff --git a/src/com/projectswg/networking/client/ClientConnectionService.java b/src/com/projectswg/networking/client/ClientConnectionService.java deleted file mode 100644 index 48a8a99..0000000 --- a/src/com/projectswg/networking/client/ClientConnectionService.java +++ /dev/null @@ -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()); - } - } - -} diff --git a/src/com/projectswg/networking/client/ClientData.java b/src/com/projectswg/networking/client/ClientData.java deleted file mode 100644 index f048269..0000000 --- a/src/com/projectswg/networking/client/ClientData.java +++ /dev/null @@ -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++; - } - -} diff --git a/src/com/projectswg/networking/client/ClientPacketSender.java b/src/com/projectswg/networking/client/ClientPacketSender.java deleted file mode 100644 index 00e81ad..0000000 --- a/src/com/projectswg/networking/client/ClientPacketSender.java +++ /dev/null @@ -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); - -} diff --git a/src/com/projectswg/networking/client/ClientServerSocket.java b/src/com/projectswg/networking/client/ClientServerSocket.java deleted file mode 100644 index 63cb65a..0000000 --- a/src/com/projectswg/networking/client/ClientServerSocket.java +++ /dev/null @@ -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; - } - } - -} diff --git a/src/com/projectswg/networking/client/receiver/ClientInboundProcessor.java b/src/com/projectswg/networking/client/receiver/ClientInboundProcessor.java deleted file mode 100644 index e966dea..0000000 --- a/src/com/projectswg/networking/client/receiver/ClientInboundProcessor.java +++ /dev/null @@ -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 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)); - } - } - -} diff --git a/src/com/projectswg/networking/client/receiver/ClientInboundService.java b/src/com/projectswg/networking/client/receiver/ClientInboundService.java deleted file mode 100644 index 67eccdd..0000000 --- a/src/com/projectswg/networking/client/receiver/ClientInboundService.java +++ /dev/null @@ -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; - } - } - -} diff --git a/src/com/projectswg/networking/client/sender/ClientOutboundService.java b/src/com/projectswg/networking/client/sender/ClientOutboundService.java deleted file mode 100644 index 2d5007f..0000000 --- a/src/com/projectswg/networking/client/sender/ClientOutboundService.java +++ /dev/null @@ -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); - } - -} diff --git a/src/com/projectswg/networking/client/sender/ClientPackager.java b/src/com/projectswg/networking/client/sender/ClientPackager.java deleted file mode 100644 index 0837731..0000000 --- a/src/com/projectswg/networking/client/sender/ClientPackager.java +++ /dev/null @@ -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 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 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 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 inboundQueue, Queue 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 outboundQueue) { - if (channel.getPacketCount() == 0) - return; - - channel.setSequence(clientData.getAndIncrementTxSequence()); - outboundQueue.add(new SequencedOutbound(channel.getSequence(), channel.encode().array())); - reset(); - } - - private void sendFragmented(Queue 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; - } - - } - -} diff --git a/src/com/projectswg/networking/client/sender/ClientPacketResender.java b/src/com/projectswg/networking/client/sender/ClientPacketResender.java deleted file mode 100644 index 7728460..0000000 --- a/src/com/projectswg/networking/client/sender/ClientPacketResender.java +++ /dev/null @@ -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 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 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 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; - } - - } -} diff --git a/src/com/projectswg/networking/client/sender/SequencedOutbound.java b/src/com/projectswg/networking/client/sender/SequencedOutbound.java deleted file mode 100644 index 0f35cca..0000000 --- a/src/com/projectswg/networking/client/sender/SequencedOutbound.java +++ /dev/null @@ -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; - } -} diff --git a/src/com/projectswg/networking/server/ServerConnectionService.java b/src/com/projectswg/networking/server/ServerConnectionService.java deleted file mode 100644 index ed73835..0000000 --- a/src/com/projectswg/networking/server/ServerConnectionService.java +++ /dev/null @@ -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 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; - } - - } - -} diff --git a/src/com/projectswg/networking/server/ServerConnectionWrapper.java b/src/com/projectswg/networking/server/ServerConnectionWrapper.java deleted file mode 100644 index bd7ec66..0000000 --- a/src/com/projectswg/networking/server/ServerConnectionWrapper.java +++ /dev/null @@ -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); - } - -} diff --git a/src/com/projectswg/resources/ClientConnectionStatus.java b/src/com/projectswg/resources/ClientConnectionStatus.java deleted file mode 100644 index 00c54cd..0000000 --- a/src/com/projectswg/resources/ClientConnectionStatus.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.projectswg.resources; - -public enum ClientConnectionStatus { - DISCONNECTED, - LOGIN_CONNECTED, - ZONE_CONNECTED -} diff --git a/src/com/projectswg/resources/HolocorePreferences.java b/src/com/projectswg/resources/HolocorePreferences.java deleted file mode 100644 index 1707d7b..0000000 --- a/src/com/projectswg/resources/HolocorePreferences.java +++ /dev/null @@ -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; - } - } - -} diff --git a/src/com/projectswg/services/PacketRecordingService.java b/src/com/projectswg/services/PacketRecordingService.java deleted file mode 100644 index 12db579..0000000 --- a/src/com/projectswg/services/PacketRecordingService.java +++ /dev/null @@ -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()); - } - } - -} diff --git a/src/main/java/com/projectswg/forwarder/ConnectionManager.java b/src/main/java/com/projectswg/forwarder/ConnectionManager.java new file mode 100644 index 0000000..970e27c --- /dev/null +++ b/src/main/java/com/projectswg/forwarder/ConnectionManager.java @@ -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 { + +} diff --git a/src/main/java/com/projectswg/forwarder/Forwarder.java b/src/main/java/com/projectswg/forwarder/Forwarder.java new file mode 100644 index 0000000..c3a2905 --- /dev/null +++ b/src/main/java/com/projectswg/forwarder/Forwarder.java @@ -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 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; + } + } + +} diff --git a/src/main/java/com/projectswg/forwarder/intents/client/ClientConnectedIntent.java b/src/main/java/com/projectswg/forwarder/intents/client/ClientConnectedIntent.java new file mode 100644 index 0000000..a44f4c9 --- /dev/null +++ b/src/main/java/com/projectswg/forwarder/intents/client/ClientConnectedIntent.java @@ -0,0 +1,11 @@ +package com.projectswg.forwarder.intents.client; + +import me.joshlarson.jlcommon.control.Intent; + +public class ClientConnectedIntent extends Intent { + + public ClientConnectedIntent() { + + } + +} diff --git a/src/main/java/com/projectswg/forwarder/intents/client/ClientDisconnectedIntent.java b/src/main/java/com/projectswg/forwarder/intents/client/ClientDisconnectedIntent.java new file mode 100644 index 0000000..7c5037d --- /dev/null +++ b/src/main/java/com/projectswg/forwarder/intents/client/ClientDisconnectedIntent.java @@ -0,0 +1,11 @@ +package com.projectswg.forwarder.intents.client; + +import me.joshlarson.jlcommon.control.Intent; + +public class ClientDisconnectedIntent extends Intent { + + public ClientDisconnectedIntent() { + + } + +} diff --git a/src/main/java/com/projectswg/forwarder/intents/client/DataPacketInboundIntent.java b/src/main/java/com/projectswg/forwarder/intents/client/DataPacketInboundIntent.java new file mode 100644 index 0000000..5d22216 --- /dev/null +++ b/src/main/java/com/projectswg/forwarder/intents/client/DataPacketInboundIntent.java @@ -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; + } + +} diff --git a/src/main/java/com/projectswg/forwarder/intents/client/DataPacketOutboundIntent.java b/src/main/java/com/projectswg/forwarder/intents/client/DataPacketOutboundIntent.java new file mode 100644 index 0000000..3b10536 --- /dev/null +++ b/src/main/java/com/projectswg/forwarder/intents/client/DataPacketOutboundIntent.java @@ -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; + } + +} diff --git a/src/main/java/com/projectswg/forwarder/intents/client/SonyPacketInboundIntent.java b/src/main/java/com/projectswg/forwarder/intents/client/SonyPacketInboundIntent.java new file mode 100644 index 0000000..ad04438 --- /dev/null +++ b/src/main/java/com/projectswg/forwarder/intents/client/SonyPacketInboundIntent.java @@ -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; + } + +} diff --git a/src/main/java/com/projectswg/forwarder/intents/client/UpdateStackIntent.java b/src/main/java/com/projectswg/forwarder/intents/client/UpdateStackIntent.java new file mode 100644 index 0000000..49a7f16 --- /dev/null +++ b/src/main/java/com/projectswg/forwarder/intents/client/UpdateStackIntent.java @@ -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; + } + +} diff --git a/src/main/java/com/projectswg/forwarder/intents/control/ClientCrashedIntent.java b/src/main/java/com/projectswg/forwarder/intents/control/ClientCrashedIntent.java new file mode 100644 index 0000000..af55df9 --- /dev/null +++ b/src/main/java/com/projectswg/forwarder/intents/control/ClientCrashedIntent.java @@ -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; + } +} diff --git a/src/main/java/com/projectswg/forwarder/intents/control/StartForwarderIntent.java b/src/main/java/com/projectswg/forwarder/intents/control/StartForwarderIntent.java new file mode 100644 index 0000000..6baff8c --- /dev/null +++ b/src/main/java/com/projectswg/forwarder/intents/control/StartForwarderIntent.java @@ -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(); + } +} diff --git a/src/main/java/com/projectswg/forwarder/intents/control/StopForwarderIntent.java b/src/main/java/com/projectswg/forwarder/intents/control/StopForwarderIntent.java new file mode 100644 index 0000000..4f713a5 --- /dev/null +++ b/src/main/java/com/projectswg/forwarder/intents/control/StopForwarderIntent.java @@ -0,0 +1,11 @@ +package com.projectswg.forwarder.intents.control; + +import me.joshlarson.jlcommon.control.Intent; + +public class StopForwarderIntent extends Intent { + + public StopForwarderIntent() { + + } + +} diff --git a/src/main/java/com/projectswg/forwarder/intents/server/ServerConnectedIntent.java b/src/main/java/com/projectswg/forwarder/intents/server/ServerConnectedIntent.java new file mode 100644 index 0000000..eb18bfd --- /dev/null +++ b/src/main/java/com/projectswg/forwarder/intents/server/ServerConnectedIntent.java @@ -0,0 +1,11 @@ +package com.projectswg.forwarder.intents.server; + +import me.joshlarson.jlcommon.control.Intent; + +public class ServerConnectedIntent extends Intent { + + public ServerConnectedIntent() { + + } + +} diff --git a/src/main/java/com/projectswg/forwarder/intents/server/ServerDisconnectedIntent.java b/src/main/java/com/projectswg/forwarder/intents/server/ServerDisconnectedIntent.java new file mode 100644 index 0000000..80bfcb5 --- /dev/null +++ b/src/main/java/com/projectswg/forwarder/intents/server/ServerDisconnectedIntent.java @@ -0,0 +1,11 @@ +package com.projectswg.forwarder.intents.server; + +import me.joshlarson.jlcommon.control.Intent; + +public class ServerDisconnectedIntent extends Intent { + + public ServerDisconnectedIntent() { + + } + +} diff --git a/src/main/java/com/projectswg/forwarder/resources/client/state/OutboundDataTuner.java b/src/main/java/com/projectswg/forwarder/resources/client/state/OutboundDataTuner.java new file mode 100644 index 0000000..0449b65 --- /dev/null +++ b/src/main/java/com/projectswg/forwarder/resources/client/state/OutboundDataTuner.java @@ -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; + } + +} diff --git a/src/main/java/com/projectswg/forwarder/resources/networking/ClientServer.java b/src/main/java/com/projectswg/forwarder/resources/networking/ClientServer.java new file mode 100644 index 0000000..973adb5 --- /dev/null +++ b/src/main/java/com/projectswg/forwarder/resources/networking/ClientServer.java @@ -0,0 +1,6 @@ +package com.projectswg.forwarder.resources.networking; + +public enum ClientServer { + LOGIN, + ZONE +} diff --git a/src/main/java/com/projectswg/forwarder/resources/networking/NetInterceptor.java b/src/main/java/com/projectswg/forwarder/resources/networking/NetInterceptor.java new file mode 100644 index 0000000..c7cbcea --- /dev/null +++ b/src/main/java/com/projectswg/forwarder/resources/networking/NetInterceptor.java @@ -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(); + } + +} diff --git a/src/com/projectswg/networking/UDPServer.java b/src/main/java/com/projectswg/forwarder/resources/networking/UDPServer.java similarity index 62% rename from src/com/projectswg/networking/UDPServer.java rename to src/main/java/com/projectswg/forwarder/resources/networking/UDPServer.java index ffdeda5..7976f47 100644 --- a/src/com/projectswg/networking/UDPServer.java +++ b/src/main/java/com/projectswg/forwarder/resources/networking/UDPServer.java @@ -25,23 +25,17 @@ * along with Holocore. If not, see . * * * ***********************************************************************************/ -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 inbound; private final AtomicBoolean running; private final InetSocketAddress bindAddr; + private final Consumer 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 callback) { + this(bindAddr, 1024, callback); } - public UDPServer(InetSocketAddress bindAddr, int packetSize) { - this.waitingForPacket = new Object(); + public UDPServer(@Nonnull InetSocketAddress bindAddr, int packetSize, @Nonnull Consumer 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 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; - } - } diff --git a/src/com/projectswg/networking/client/receiver/ClientFragmentedProcessor.java b/src/main/java/com/projectswg/forwarder/resources/networking/data/FragmentedProcessor.java similarity index 80% rename from src/com/projectswg/networking/client/receiver/ClientFragmentedProcessor.java rename to src/main/java/com/projectswg/forwarder/resources/networking/data/FragmentedProcessor.java index bc8b8d4..833eddc 100644 --- a/src/com/projectswg/networking/client/receiver/ClientFragmentedProcessor.java +++ b/src/main/java/com/projectswg/forwarder/resources/networking/data/FragmentedProcessor.java @@ -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 fragmentedBuffer; - public ClientFragmentedProcessor() { + public FragmentedProcessor() { this.fragmentedBuffer = new ArrayList<>(); } diff --git a/src/main/java/com/projectswg/forwarder/resources/networking/data/Packager.java b/src/main/java/com/projectswg/forwarder/resources/networking/data/Packager.java new file mode 100644 index 0000000..0fd2943 --- /dev/null +++ b/src/main/java/com/projectswg/forwarder/resources/networking/data/Packager.java @@ -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 outboundRaw; + private final Queue outboundPackaged; + private final ProtocolStack stack; + + public Packager(Queue outboundRaw, Queue 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; + } + +} diff --git a/src/main/java/com/projectswg/forwarder/resources/networking/data/ProtocolStack.java b/src/main/java/com/projectswg/forwarder/resources/networking/data/ProtocolStack.java new file mode 100644 index 0000000..7d9b259 --- /dev/null +++ b/src/main/java/com/projectswg/forwarder/resources/networking/data/ProtocolStack.java @@ -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 sequenced; + private final FragmentedProcessor fragmentedProcessor; + private final InetSocketAddress source; + private final BiConsumer sender; + private final ClientServer server; + private final Queue outboundRaw; + private final Queue 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 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 getOutboundPackagedBuffer() { + return Collections.unmodifiableCollection(outboundPackaged); + } + +} diff --git a/src/main/java/com/projectswg/forwarder/resources/networking/data/SequencedOutbound.java b/src/main/java/com/projectswg/forwarder/resources/networking/data/SequencedOutbound.java new file mode 100644 index 0000000..6f01690 --- /dev/null +++ b/src/main/java/com/projectswg/forwarder/resources/networking/data/SequencedOutbound.java @@ -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; + } + +} diff --git a/src/com/projectswg/networking/encryption/Compression.java b/src/main/java/com/projectswg/forwarder/resources/networking/encryption/Compression.java similarity index 94% rename from src/com/projectswg/networking/encryption/Compression.java rename to src/main/java/com/projectswg/forwarder/resources/networking/encryption/Compression.java index b244a1b..f830c01 100644 --- a/src/com/projectswg/networking/encryption/Compression.java +++ b/src/main/java/com/projectswg/forwarder/resources/networking/encryption/Compression.java @@ -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; diff --git a/src/com/projectswg/networking/encryption/Encryption.java b/src/main/java/com/projectswg/forwarder/resources/networking/encryption/Encryption.java similarity index 98% rename from src/com/projectswg/networking/encryption/Encryption.java rename to src/main/java/com/projectswg/forwarder/resources/networking/encryption/Encryption.java index 52cbdef..064752b 100644 --- a/src/com/projectswg/networking/encryption/Encryption.java +++ b/src/main/java/com/projectswg/forwarder/resources/networking/encryption/Encryption.java @@ -25,14 +25,15 @@ * along with Holocore. If not, see . * * * ***********************************************************************************/ -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 { diff --git a/src/com/projectswg/networking/encryption/EncryptionCRC.java b/src/main/java/com/projectswg/forwarder/resources/networking/encryption/EncryptionCRC.java similarity index 96% rename from src/com/projectswg/networking/encryption/EncryptionCRC.java rename to src/main/java/com/projectswg/forwarder/resources/networking/encryption/EncryptionCRC.java index 56d83ac..445397b 100644 --- a/src/com/projectswg/networking/encryption/EncryptionCRC.java +++ b/src/main/java/com/projectswg/forwarder/resources/networking/encryption/EncryptionCRC.java @@ -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 . * -* * -***********************************************************************************/ -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 . * +* * +***********************************************************************************/ +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; diff --git a/src/com/projectswg/networking/encryption/MD5.java b/src/main/java/com/projectswg/forwarder/resources/networking/encryption/MD5.java similarity index 94% rename from src/com/projectswg/networking/encryption/MD5.java rename to src/main/java/com/projectswg/forwarder/resources/networking/encryption/MD5.java index c2dab27..563dddb 100644 --- a/src/com/projectswg/networking/encryption/MD5.java +++ b/src/main/java/com/projectswg/forwarder/resources/networking/encryption/MD5.java @@ -25,13 +25,14 @@ * along with Holocore. If not, see . * * * ***********************************************************************************/ -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(); } -} \ No newline at end of file +} diff --git a/src/com/projectswg/networking/soe/Acknowledge.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/Acknowledge.java similarity index 94% rename from src/com/projectswg/networking/soe/Acknowledge.java rename to src/main/java/com/projectswg/forwarder/resources/networking/packets/Acknowledge.java index d35cf94..2eea00a 100644 --- a/src/com/projectswg/networking/soe/Acknowledge.java +++ b/src/main/java/com/projectswg/forwarder/resources/networking/packets/Acknowledge.java @@ -25,13 +25,10 @@ * along with Holocore. If not, see . * * * ***********************************************************************************/ -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); } diff --git a/src/com/projectswg/networking/soe/ClientNetworkStatusUpdate.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/ClientNetworkStatusUpdate.java similarity index 98% rename from src/com/projectswg/networking/soe/ClientNetworkStatusUpdate.java rename to src/main/java/com/projectswg/forwarder/resources/networking/packets/ClientNetworkStatusUpdate.java index 79295c3..5c4c080 100644 --- a/src/com/projectswg/networking/soe/ClientNetworkStatusUpdate.java +++ b/src/main/java/com/projectswg/forwarder/resources/networking/packets/ClientNetworkStatusUpdate.java @@ -25,13 +25,10 @@ * along with Holocore. If not, see . * * * ***********************************************************************************/ -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; diff --git a/src/com/projectswg/networking/soe/DataChannel.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/DataChannel.java similarity index 98% rename from src/com/projectswg/networking/soe/DataChannel.java rename to src/main/java/com/projectswg/forwarder/resources/networking/packets/DataChannel.java index dbe52b4..b9d3bcd 100644 --- a/src/com/projectswg/networking/soe/DataChannel.java +++ b/src/main/java/com/projectswg/forwarder/resources/networking/packets/DataChannel.java @@ -25,14 +25,13 @@ * along with Holocore. If not, see . * * * ***********************************************************************************/ -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 { diff --git a/src/com/projectswg/networking/soe/Disconnect.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/Disconnect.java similarity index 97% rename from src/com/projectswg/networking/soe/Disconnect.java rename to src/main/java/com/projectswg/forwarder/resources/networking/packets/Disconnect.java index f206e15..ab393cd 100644 --- a/src/com/projectswg/networking/soe/Disconnect.java +++ b/src/main/java/com/projectswg/forwarder/resources/networking/packets/Disconnect.java @@ -25,13 +25,10 @@ * along with Holocore. If not, see . * * * ***********************************************************************************/ -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; diff --git a/src/com/projectswg/networking/soe/Fragmented.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/Fragmented.java similarity index 98% rename from src/com/projectswg/networking/soe/Fragmented.java rename to src/main/java/com/projectswg/forwarder/resources/networking/packets/Fragmented.java index 633ecea..de01538 100644 --- a/src/com/projectswg/networking/soe/Fragmented.java +++ b/src/main/java/com/projectswg/forwarder/resources/networking/packets/Fragmented.java @@ -25,13 +25,10 @@ * along with Holocore. If not, see . * * * ***********************************************************************************/ -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; diff --git a/src/com/projectswg/networking/soe/KeepAlive.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/KeepAlive.java similarity index 81% rename from src/com/projectswg/networking/soe/KeepAlive.java rename to src/main/java/com/projectswg/forwarder/resources/networking/packets/KeepAlive.java index a710288..9a42263 100644 --- a/src/com/projectswg/networking/soe/KeepAlive.java +++ b/src/main/java/com/projectswg/forwarder/resources/networking/packets/KeepAlive.java @@ -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() { diff --git a/src/com/projectswg/networking/soe/MultiPacket.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/MultiPacket.java similarity index 98% rename from src/com/projectswg/networking/soe/MultiPacket.java rename to src/main/java/com/projectswg/forwarder/resources/networking/packets/MultiPacket.java index 88d2f69..2ca4490 100644 --- a/src/com/projectswg/networking/soe/MultiPacket.java +++ b/src/main/java/com/projectswg/forwarder/resources/networking/packets/MultiPacket.java @@ -25,15 +25,13 @@ * along with Holocore. If not, see . * * * ***********************************************************************************/ -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 { diff --git a/src/com/projectswg/networking/soe/OutOfOrder.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/OutOfOrder.java similarity index 97% rename from src/com/projectswg/networking/soe/OutOfOrder.java rename to src/main/java/com/projectswg/forwarder/resources/networking/packets/OutOfOrder.java index 808fa4d..0e59fb6 100644 --- a/src/com/projectswg/networking/soe/OutOfOrder.java +++ b/src/main/java/com/projectswg/forwarder/resources/networking/packets/OutOfOrder.java @@ -25,13 +25,10 @@ * along with Holocore. If not, see . * * * ***********************************************************************************/ -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; diff --git a/src/com/projectswg/networking/Packet.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/Packet.java similarity index 99% rename from src/com/projectswg/networking/Packet.java rename to src/main/java/com/projectswg/forwarder/resources/networking/packets/Packet.java index c6c44fe..e7bc9e3 100644 --- a/src/com/projectswg/networking/Packet.java +++ b/src/main/java/com/projectswg/forwarder/resources/networking/packets/Packet.java @@ -25,7 +25,7 @@ * along with Holocore. If not, see . * * * ***********************************************************************************/ -package com.projectswg.networking; +package com.projectswg.forwarder.resources.networking.packets; import java.net.InetAddress; import java.nio.ByteBuffer; diff --git a/src/main/java/com/projectswg/forwarder/resources/networking/packets/PingPacket.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/PingPacket.java new file mode 100644 index 0000000..c93260d --- /dev/null +++ b/src/main/java/com/projectswg/forwarder/resources/networking/packets/PingPacket.java @@ -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; + } + +} diff --git a/src/com/projectswg/utilities/ByteUtilities.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/RawSWGPacket.java similarity index 56% rename from src/com/projectswg/utilities/ByteUtilities.java rename to src/main/java/com/projectswg/forwarder/resources/networking/packets/RawSWGPacket.java index 3ee3564..a048dcf 100644 --- a/src/com/projectswg/utilities/ByteUtilities.java +++ b/src/main/java/com/projectswg/forwarder/resources/networking/packets/RawSWGPacket.java @@ -25,72 +25,17 @@ * along with Holocore. If not, see . * * * ***********************************************************************************/ -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(); - } } diff --git a/src/com/projectswg/networking/soe/SequencedPacket.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/SequencedPacket.java similarity index 60% rename from src/com/projectswg/networking/soe/SequencedPacket.java rename to src/main/java/com/projectswg/forwarder/resources/networking/packets/SequencedPacket.java index 8661e86..3c432d7 100644 --- a/src/com/projectswg/networking/soe/SequencedPacket.java +++ b/src/main/java/com/projectswg/forwarder/resources/networking/packets/SequencedPacket.java @@ -1,4 +1,4 @@ -package com.projectswg.networking.soe; +package com.projectswg.forwarder.resources.networking.packets; public interface SequencedPacket extends Comparable { short getSequence(); diff --git a/src/com/projectswg/networking/soe/SeriousErrorAcknowledge.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/SeriousErrorAcknowledge.java similarity index 82% rename from src/com/projectswg/networking/soe/SeriousErrorAcknowledge.java rename to src/main/java/com/projectswg/forwarder/resources/networking/packets/SeriousErrorAcknowledge.java index 026ce6a..1201373 100644 --- a/src/com/projectswg/networking/soe/SeriousErrorAcknowledge.java +++ b/src/main/java/com/projectswg/forwarder/resources/networking/packets/SeriousErrorAcknowledge.java @@ -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() { diff --git a/src/com/projectswg/networking/soe/SeriousErrorAcknowledgeReply.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/SeriousErrorAcknowledgeReply.java similarity index 83% rename from src/com/projectswg/networking/soe/SeriousErrorAcknowledgeReply.java rename to src/main/java/com/projectswg/forwarder/resources/networking/packets/SeriousErrorAcknowledgeReply.java index 9356ec1..5eea8d1 100644 --- a/src/com/projectswg/networking/soe/SeriousErrorAcknowledgeReply.java +++ b/src/main/java/com/projectswg/forwarder/resources/networking/packets/SeriousErrorAcknowledgeReply.java @@ -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() { diff --git a/src/com/projectswg/networking/soe/ServerNetworkStatusUpdate.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/ServerNetworkStatusUpdate.java similarity index 98% rename from src/com/projectswg/networking/soe/ServerNetworkStatusUpdate.java rename to src/main/java/com/projectswg/forwarder/resources/networking/packets/ServerNetworkStatusUpdate.java index afcf974..ba79b15 100644 --- a/src/com/projectswg/networking/soe/ServerNetworkStatusUpdate.java +++ b/src/main/java/com/projectswg/forwarder/resources/networking/packets/ServerNetworkStatusUpdate.java @@ -25,12 +25,10 @@ * along with Holocore. If not, see . * * * ***********************************************************************************/ -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 { diff --git a/src/com/projectswg/networking/soe/SessionRequest.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/SessionRequest.java similarity index 97% rename from src/com/projectswg/networking/soe/SessionRequest.java rename to src/main/java/com/projectswg/forwarder/resources/networking/packets/SessionRequest.java index e72d156..a3fa988 100644 --- a/src/com/projectswg/networking/soe/SessionRequest.java +++ b/src/main/java/com/projectswg/forwarder/resources/networking/packets/SessionRequest.java @@ -25,13 +25,11 @@ * along with Holocore. If not, see . * * * ***********************************************************************************/ -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 { diff --git a/src/com/projectswg/networking/soe/SessionResponse.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/SessionResponse.java similarity index 98% rename from src/com/projectswg/networking/soe/SessionResponse.java rename to src/main/java/com/projectswg/forwarder/resources/networking/packets/SessionResponse.java index 49c20c1..47a4ef3 100644 --- a/src/com/projectswg/networking/soe/SessionResponse.java +++ b/src/main/java/com/projectswg/forwarder/resources/networking/packets/SessionResponse.java @@ -25,14 +25,11 @@ * along with Holocore. If not, see . * * * ***********************************************************************************/ -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; diff --git a/src/com/projectswg/recording/PacketRecorder.java b/src/main/java/com/projectswg/forwarder/resources/recording/PacketRecorder.java similarity index 67% rename from src/com/projectswg/recording/PacketRecorder.java rename to src/main/java/com/projectswg/forwarder/resources/recording/PacketRecorder.java index e540611..8f204ef 100644 --- a/src/com/projectswg/recording/PacketRecorder.java +++ b/src/main/java/com/projectswg/forwarder/resources/recording/PacketRecorder.java @@ -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); diff --git a/src/main/java/com/projectswg/forwarder/services/client/ClientConnectionManager.java b/src/main/java/com/projectswg/forwarder/services/client/ClientConnectionManager.java new file mode 100644 index 0000000..a4b25b3 --- /dev/null +++ b/src/main/java/com/projectswg/forwarder/services/client/ClientConnectionManager.java @@ -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 { + +} diff --git a/src/main/java/com/projectswg/forwarder/services/client/ClientInboundDataService.java b/src/main/java/com/projectswg/forwarder/services/client/ClientInboundDataService.java new file mode 100644 index 0000000..e2ae627 --- /dev/null +++ b/src/main/java/com/projectswg/forwarder/services/client/ClientInboundDataService.java @@ -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 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)); + } + +} diff --git a/src/main/java/com/projectswg/forwarder/services/client/ClientOutboundDataService.java b/src/main/java/com/projectswg/forwarder/services/client/ClientOutboundDataService.java new file mode 100644 index 0000000..0470418 --- /dev/null +++ b/src/main/java/com/projectswg/forwarder/services/client/ClientOutboundDataService.java @@ -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 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); + } + } + +} diff --git a/src/main/java/com/projectswg/forwarder/services/client/ClientProtocolService.java b/src/main/java/com/projectswg/forwarder/services/client/ClientProtocolService.java new file mode 100644 index 0000000..bbf6b41 --- /dev/null +++ b/src/main/java/com/projectswg/forwarder/services/client/ClientProtocolService.java @@ -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 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()); + } + +} diff --git a/src/main/java/com/projectswg/forwarder/services/client/ClientServerService.java b/src/main/java/com/projectswg/forwarder/services/client/ClientServerService.java new file mode 100644 index 0000000..96caabd --- /dev/null +++ b/src/main/java/com/projectswg/forwarder/services/client/ClientServerService.java @@ -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 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; + } + } + +} diff --git a/src/main/java/com/projectswg/forwarder/services/crash/CrashManager.java b/src/main/java/com/projectswg/forwarder/services/crash/CrashManager.java new file mode 100644 index 0000000..135ee49 --- /dev/null +++ b/src/main/java/com/projectswg/forwarder/services/crash/CrashManager.java @@ -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 { + +} diff --git a/src/main/java/com/projectswg/forwarder/services/crash/IntentRecordingService.java b/src/main/java/com/projectswg/forwarder/services/crash/IntentRecordingService.java new file mode 100644 index 0000000..c8e3f1c --- /dev/null +++ b/src/main/java/com/projectswg/forwarder/services/crash/IntentRecordingService.java @@ -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"); + } + } + +} diff --git a/src/main/java/com/projectswg/forwarder/services/crash/PacketRecordingService.java b/src/main/java/com/projectswg/forwarder/services/crash/PacketRecordingService.java new file mode 100644 index 0000000..fd75337 --- /dev/null +++ b/src/main/java/com/projectswg/forwarder/services/crash/PacketRecordingService.java @@ -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()); + } + +} diff --git a/src/main/java/com/projectswg/forwarder/services/server/ServerConnectionManager.java b/src/main/java/com/projectswg/forwarder/services/server/ServerConnectionManager.java new file mode 100644 index 0000000..d65d447 --- /dev/null +++ b/src/main/java/com/projectswg/forwarder/services/server/ServerConnectionManager.java @@ -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 { + +} diff --git a/src/main/java/com/projectswg/forwarder/services/server/ServerConnectionService.java b/src/main/java/com/projectswg/forwarder/services/server/ServerConnectionService.java new file mode 100644 index 0000000..760ff0f --- /dev/null +++ b/src/main/java/com/projectswg/forwarder/services/server/ServerConnectionService.java @@ -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"); + } + +}