Merge branch 'master' of bitbucket.org:projectswg/pswgcommon

This commit is contained in:
Obique PSWG
2018-02-04 08:30:40 -06:00
23 changed files with 1445 additions and 362 deletions

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-9"/>
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
<classpathentry kind="output" path="bin"/>
</classpath>

11
.gitignore vendored
View File

@@ -1,4 +1,13 @@
.settings
.gradle
# IDE-based
PSWGCommon.iml
out
.idea
# Gradle
bin
build
.gradle
gradle
gradlew*

View File

@@ -1,12 +1,12 @@
apply plugin: 'java'
plugins {
id 'java'
id 'idea'
}
sourceSets {
main {
java {
srcDirs = ['src']
}
resources {
srcDirs = ['src']
srcDir 'src'
}
}
}
@@ -15,3 +15,7 @@ jar {
from sourceSets.main.allSource
archiveName = "PSWGCommon.jar"
}
task wrapper(type: Wrapper) {
gradleVersion = "4.4"
}

3
module-info.java Normal file
View File

@@ -0,0 +1,3 @@
module com.projectswg.launcher {
requires java.base;
}

View File

@@ -27,6 +27,9 @@
***********************************************************************************/
package com.projectswg.common.control;
import com.projectswg.common.concurrency.PswgThreadPool;
import com.projectswg.common.debug.Log;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -39,9 +42,6 @@ import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import com.projectswg.common.concurrency.PswgThreadPool;
import com.projectswg.common.debug.Log;
public class IntentManager {
private static final AtomicReference<IntentManager> INSTANCE = new AtomicReference<>(null);
@@ -70,6 +70,7 @@ public class IntentManager {
if (!initialized.getAndSet(false))
return;
processThreads.stop(true);
processThreads.awaitTermination(1000);
}
public int getIntentCount() {

View File

@@ -160,7 +160,9 @@ public abstract class Manager extends Service {
return;
}
children.add(s);
s.setIntentManager(getIntentManager());
IntentManager manager = getIntentManager();
if (manager != null)
s.setIntentManager(manager);
}
}

View File

@@ -0,0 +1,73 @@
/***********************************************************************************
* Copyright (c) 2018 /// Project SWG /// www.projectswg.com *
* *
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
* Our goal is to create an emulator which will provide a server for players to *
* continue playing a game similar to the one they used to play. We are basing *
* it on the final publish of the game prior to end-game events. *
* *
* This file is part of Holocore. *
* *
* --------------------------------------------------------------------------------*
* *
* Holocore is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* Holocore is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
package com.projectswg.common.control;
public class PrimaryManager {
private final Manager manager;
public PrimaryManager() {
this(new DefaultManager());
}
public PrimaryManager(Manager manager) {
this.manager = manager;
}
public void addChildService(Service service) {
manager.addChildService(service);
}
public boolean initialize() {
return manager.initialize();
}
public boolean start() {
boolean ret = manager.start();
if (!ret)
terminate();
return ret;
}
public boolean isOperational() {
return manager.isOperational();
}
public boolean stop() {
return manager.stop();
}
public boolean terminate() {
return manager.terminate();
}
private static class DefaultManager extends Manager {
}
}

View File

@@ -74,7 +74,9 @@ public abstract class Service {
* @return TRUE if termination was successful, FALSE otherwise
*/
public boolean terminate() {
IntentManager.getInstance().terminate();
IntentManager im = IntentManager.getInstance();
if (im != null)
im.terminate();
return true;
}

View File

@@ -1,39 +1,39 @@
/***********************************************************************************
* Copyright (c) 2015 /// Project SWG /// www.projectswg.com *
* *
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
* Our goal is to create an emulator which will provide a server for players to *
* continue playing a game similar to the one they used to play. We are basing *
* it on the final publish of the game prior to end-game events. *
* *
* This file is part of Holocore. *
* *
* -------------------------------------------------------------------------------- *
* *
* Holocore is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* Holocore is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
* Copyright (c) 2017 /// Project SWG /// www.projectswg.com *
* *
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
* Our goal is to create an emulator which will provide a server for players to *
* continue playing a game similar to the one they used to play. We are basing *
* it on the final publish of the game prior to end-game events. *
* *
* This file is part of Holocore. *
* *
* --------------------------------------------------------------------------------*
* *
* Holocore is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* Holocore is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
package com.projectswg.common.data;
import java.nio.charset.StandardCharsets;
import com.projectswg.common.encoding.Encodable;
import com.projectswg.common.network.NetBuffer;
import com.projectswg.common.network.NetBufferStream;
import com.projectswg.common.persistable.Persistable;
import java.nio.charset.StandardCharsets;
public class CRC implements Encodable, Persistable {
private static final int CRC_TABLE[] = {
@@ -101,7 +101,7 @@ public class CRC implements Encodable, Persistable {
}
@Override
public byte [] encode() {
public byte[] encode() {
NetBuffer buffer = NetBuffer.allocate(4);
buffer.addInt(crc);
return buffer.array();
@@ -127,45 +127,35 @@ public class CRC implements Encodable, Persistable {
public void read(NetBufferStream stream) {
crc = stream.getInt();
}
@Override
public String toString() {
return str;
}
@Override
public int hashCode() {
return this.crc;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof CRC)) {
return false;
}
return crc == ((CRC) obj).crc;
}
public static int getCrc(String input) {
return getCrc(input.getBytes(StandardCharsets.UTF_8));
}
public static int getCrc(byte [] data) {
int crc = 0xFFFFFFFF;
for (int i = 0; i < data.length; i++)
crc = CRC_TABLE[((crc>>>24) ^ data[i]) & 0xFF] ^ (crc << 8);
return ~crc;
return this == obj || (obj instanceof CRC && crc == ((CRC) obj).crc);
}
public static String getString(int crc) {
return CrcDatabase.getInstance().getString(crc);
}
public static int getCrc(String input) {
return getCrc(input.getBytes(StandardCharsets.UTF_8));
}
public static int getCrc(byte[] data) {
int crc = 0xFFFFFFFF;
for (byte b : data)
crc = CRC_TABLE[((crc >>> 24) ^ b) & 0xFF] ^ (crc << 8);
return ~crc;
}
}

View File

@@ -0,0 +1,192 @@
package com.projectswg.common.data;
import java.util.Objects;
import com.projectswg.common.debug.Assert;
import com.projectswg.common.debug.Log;
import com.projectswg.common.encoding.CachedEncode;
import com.projectswg.common.encoding.Encodable;
import com.projectswg.common.encoding.Encoder;
import com.projectswg.common.encoding.StringType;
import com.projectswg.common.network.NetBuffer;
import com.projectswg.common.network.NetBufferStream;
import com.projectswg.common.persistable.Persistable;
public class Pair<T, S> implements Encodable, Persistable {
private final StringType leftType;
private final StringType rightType;
private final Class<T> leftClass;
private final Class<S> rightClass;
private final CachedEncode cache;
private T left;
private S right;
private Pair(T left, S right, Class<T> leftClass, Class<S> rightClass, StringType leftType, StringType rightType) {
// Final checks - has to be either one or the other. These ensure other assumptions in the class will succeed
Assert.test((leftType == null && !(left instanceof String) && !leftClass.equals(String.class)) ^ (leftType != null && (left instanceof String) && leftClass.equals(String.class)));
Assert.test((rightType == null && !(right instanceof String) && !rightClass.equals(String.class)) ^ (rightType != null && (right instanceof String) && rightClass.equals(String.class)));
this.leftClass = leftClass;
this.rightClass = rightClass;
this.leftType = leftType;
this.rightType = rightType;
this.cache = new CachedEncode(this::encodeImpl);
setLeft(left);
setRight(right);
}
public T getLeft() {
return left;
}
public S getRight() {
return right;
}
public void setLeft(T val1) {
this.left = val1;
cache.clearCached();
}
public void setRight(S val2) {
this.right = val2;
cache.clearCached();
}
@Override
public void decode(NetBuffer data) {
setLeft(smartDecode(data, leftClass, leftType));
setRight(smartDecode(data, rightClass, rightType));
}
@Override
public byte[] encode() {
Objects.requireNonNull(left, "left is null in encode()");
Objects.requireNonNull(right, "right is null in encode()");
return cache.encode();
}
@Override
public int getLength() {
return cache.encode().length;
}
@Override
public void save(NetBufferStream stream) {
smartSave(stream, left, leftType);
smartSave(stream, right, rightType);
}
@Override
public void read(NetBufferStream stream) {
smartRead(stream, left, leftClass, leftType);
smartRead(stream, right, rightClass, rightType);
}
private byte [] encodeImpl() {
if (left == null || right == null)
return new byte[0];
byte [] left = smartEncode(this.left, leftType);
byte [] right = smartEncode(this.right, rightType);
byte [] combined = new byte[left.length + right.length];
System.arraycopy(left, 0, combined, 0, left.length);
System.arraycopy(right, 0, combined, left.length, right.length);
return combined;
}
@SuppressWarnings("unchecked") // should succeed based on checks in constructor
private static <U> U smartDecode(NetBuffer data, Class<U> klass, StringType strType) {
if (strType != null)
return (U) data.getString(strType);
return (U) data.getGeneric(klass);
}
private static byte [] smartEncode(Object obj, StringType strType) {
if (strType != null)
return Encoder.encode(obj, strType);
return Encoder.encode(obj);
}
@SuppressWarnings("unchecked") // should succeed based on checks in constructor
private static <U> U smartRead(NetBufferStream data, U obj, Class<U> klass, StringType strType) {
if (strType != null)
return (U) data.getString(strType);
// Try making our own persistable
if (obj == null && Persistable.class.isAssignableFrom(klass)) {
try {
obj = klass.getConstructor().newInstance();
} catch (Exception e) {
Log.e(e);
}
}
// If we succeeded, read it - if not, try grabbing some generic
if (obj instanceof Persistable) {
((Persistable) obj).read(data);
} else {
obj = (U) data.getGeneric(klass);
}
return obj;
}
private static void smartSave(NetBufferStream data, Object obj, StringType strType) {
if (strType != null) {
data.addString((String) obj, strType);
} else if (obj instanceof Persistable) {
((Persistable) obj).save(data);
} else {
data.write(Encoder.encode(obj));
}
}
@SuppressWarnings("unchecked") // it's pretty obvious T.getClass() should be Class<T>
public static <T, S> Pair<T, S> createPair(T left, S right) {
Objects.requireNonNull(left, "Left cannot be null in this method! Instead call createPair(left, right, leftClass, rightClass)");
Objects.requireNonNull(right, "Right cannot be null in this method! Instead call createPair(left, right, leftClass, rightClass)");
return createPair(left, right, (Class<T>) left.getClass(), (Class<S>) right.getClass());
}
public static <T, S> Pair<T, S> createPair(T left, S right, Class<T> leftClass, Class<S> rightClass) {
Assert.test(!(left instanceof String));
Assert.test(!(right instanceof String));
return new Pair<>(left, right, leftClass, rightClass, null, null);
}
@SuppressWarnings("unchecked") // it's pretty obvious T.getClass() should be Class<T>
public static <T> Pair<String, T> createPair(String left, T right, StringType type) {
Objects.requireNonNull(right, "Right cannot be null in this method! Instead call createPair(left, right, type, rightClass)");
return createPair(left, right, type, (Class<T>) right.getClass());
}
public static <T> Pair<String, T> createPair(String left, T right, StringType type, Class<T> rightClass) {
Assert.test(!(right instanceof String));
return new Pair<>(left, right, String.class, rightClass, type, null);
}
@SuppressWarnings("unchecked") // it's pretty obvious T.getClass() should be Class<T>
public static <T> Pair<T, String> createPair(T left, String right, StringType type) {
Objects.requireNonNull(left, "Left cannot be null in this method! Instead call createPair(left, right, leftClass, type)");
return createPair(left, right, (Class<T>) left.getClass(), type);
}
public static <T> Pair<T, String> createPair(T left, String right, Class<T> leftClass, StringType type) {
Assert.test(!(left instanceof String));
return new Pair<>(left, right, leftClass, String.class, null, type);
}
/**
* Creates a pair with two strings
* @param left the left string, can be null
* @param right the right string, can be null
* @param leftType the type of the left string: ASCII/UNICODE
* @param rightType the type of the right string: ASCII/UNICODE
* @return the corresponding pair
*/
public static Pair<String, String> createPair(String left, String right, StringType leftType, StringType rightType) {
return new Pair<>(left, right, String.class, String.class, leftType, rightType);
}
}

View File

@@ -349,7 +349,8 @@ public enum RadialItem {
BIO_LINK (""),
LANDMINE_DISARM (""),
LANDMINE_REVERSE_TRIGGER (""),
REWARD_TRADE_IN ("");
REWARD_TRADE_IN (""),
TRANSFER_CREDITS_TO_BANK_ACCOUNT ("@space/space_loot:use_credit_chip");
private static final EnumLookup<Integer, RadialItem> LOOKUP = new EnumLookup<>(RadialItem.class, RadialItem::getId);

View File

@@ -0,0 +1,61 @@
/***********************************************************************************
* Copyright (c) 2018 /// Project SWG /// www.projectswg.com *
* *
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
* Our goal is to create an emulator which will provide a server for players to *
* continue playing a game similar to the one they used to play. We are basing *
* it on the final publish of the game prior to end-game events. *
* *
* This file is part of Holocore. *
* *
* --------------------------------------------------------------------------------*
* *
* Holocore is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* Holocore is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
package com.projectswg.common.debug;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class ThreadPrinter {
public static void printActiveThreads() {
ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
int threadCount = threadGroup.activeCount();
Thread[] threadsRaw = new Thread[threadCount];
threadCount = threadGroup.enumerate(threadsRaw);
List<Thread> threads = Arrays.stream(threadsRaw, 0, threadCount).filter(t -> t.getState() != Thread.State.TERMINATED).collect(Collectors.toList());
int maxLength = threads.stream().mapToInt(t -> t.getName().length()).max().orElse(4);
if (maxLength < 4)
maxLength = 4;
Log.w("Active Threads: %d", threads.size());
Log.w("+-%s---%s-+", createRepeatingDash(maxLength), createRepeatingDash(13));
Log.w("| %-" + maxLength + "s | %-13s |", "Name", "State");
Log.w("+-%s-+-%s-+", createRepeatingDash(maxLength), createRepeatingDash(13));
for (Thread t : threads) {
Log.w("| %-" + maxLength + "s | %-13s |", t.getName(), t.getState());
}
Log.w("+-%s---%s-+", createRepeatingDash(maxLength), createRepeatingDash(13));
}
private static String createRepeatingDash(int count) {
return String.join("", Collections.nCopies(count, "-"));
}
}

View File

@@ -18,11 +18,6 @@ public class FileLogWrapper implements LogWrapper {
writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8));
}
@Override
protected void finalize() throws IOException {
writer.close();
}
@Override
public void onLog(LogLevel level, String str) {
try {

View File

@@ -27,6 +27,7 @@
***********************************************************************************/
package com.projectswg.common.network;
import java.lang.reflect.InvocationTargetException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
@@ -208,7 +209,7 @@ public class NetBuffer {
}
public boolean getBoolean() {
return getByte() == 1 ? true : false;
return getByte() == 1;
}
public String getAscii() {
@@ -298,16 +299,16 @@ public class NetBuffer {
return booleans;
}
public <T> Object getGeneric(Class<T> type) {
public Object getGeneric(Class<?> type) {
if (Encodable.class.isAssignableFrom(type)) {
T instance = null;
Object instance = null;
try {
instance = type.newInstance();
instance = type.getConstructor().newInstance();
((Encodable) instance).decode(this);
} catch (InstantiationException | IllegalAccessException e) {
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
Log.e(e);
}
return instance;
} else if (Integer.class.isAssignableFrom(type) || Integer.TYPE.isAssignableFrom(type))
return getInt();
@@ -336,11 +337,11 @@ public class NetBuffer {
try {
for (int i = 0; i < size; i++) {
T instance = type.newInstance();
T instance = type.getConstructor().newInstance();
instance.decode(this);
list.add(instance);
}
} catch (InstantiationException | IllegalAccessException e) {
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
Log.e(e);
}
@@ -383,9 +384,9 @@ public class NetBuffer {
public <T extends Encodable> T getEncodable(Class<T> type) {
T instance = null;
try {
instance = type.newInstance();
instance = type.getConstructor().newInstance();
instance.decode(this);
} catch (InstantiationException | IllegalAccessException e) {
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
Log.e(e);
}

View File

@@ -34,6 +34,8 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.projectswg.common.encoding.StringType;
public class NetBufferStream extends OutputStream {
private final Object expansionMutex;
@@ -189,6 +191,23 @@ public class NetBufferStream extends OutputStream {
}
}
public Object getGeneric(Class<?> type) {
synchronized (bufferMutex) {
return buffer.getGeneric(type);
}
}
public String getString(StringType type) {
switch (type) {
case ASCII:
return getAscii();
case UNICODE:
return getUnicode();
default:
throw new IllegalArgumentException("Unknown string type: " + type);
}
}
public String getAscii() {
synchronized (bufferMutex) {
return buffer.getAscii();
@@ -261,6 +280,12 @@ public class NetBufferStream extends OutputStream {
}
}
public byte[] getArrayLarge() {
synchronized (bufferMutex) {
return buffer.getArrayLarge();
}
}
public byte[] getArray(int size) {
synchronized (bufferMutex) {
return buffer.getArray(size);
@@ -281,6 +306,19 @@ public class NetBufferStream extends OutputStream {
}
}
public void addString(String s, StringType type) {
switch (type) {
case ASCII:
addAscii(s);
break;
case UNICODE:
addUnicode(s);
break;
default:
throw new IllegalArgumentException("Unknown string type: " + type);
}
}
public void addAscii(String s) {
ensureCapacity(size+2+s.length());
synchronized (bufferMutex) {

View File

@@ -0,0 +1,140 @@
package com.projectswg.common.network;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketImpl;
import java.net.SocketImplFactory;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
public class SecureSocketFactory extends SSLSocketFactory implements SocketImplFactory {
private final AtomicBoolean loaded;
private SSLSocketFactory sslSocketFactory;
public SecureSocketFactory() {
this.loaded = new AtomicBoolean(false);
this.sslSocketFactory = null;
}
@Override
public Socket createSocket(Socket arg0, InputStream arg1, boolean arg2) throws IOException {
return sslSocketFactory.createSocket(arg0, arg1, arg2);
}
@Override
public Socket createSocket(Socket arg0, String arg1, int arg2, boolean arg3) throws IOException {
return sslSocketFactory.createSocket(arg0, arg1, arg2, arg3);
}
@Override
public Socket createSocket(String arg0, int arg1) throws IOException, UnknownHostException {
return sslSocketFactory.createSocket(arg0, arg1);
}
@Override
public Socket createSocket(InetAddress arg0, int arg1) throws IOException {
return sslSocketFactory.createSocket(arg0, arg1);
}
@Override
public Socket createSocket(String arg0, int arg1, InetAddress arg2, int arg3) throws IOException, UnknownHostException {
return sslSocketFactory.createSocket(arg0, arg1, arg2, arg3);
}
@Override
public Socket createSocket(InetAddress arg0, int arg1, InetAddress arg2, int arg3) throws IOException {
return sslSocketFactory.createSocket(arg0, arg1, arg2, arg3);
}
@Override
public boolean equals(Object obj) {
return sslSocketFactory.equals(obj);
}
@Override
public String[] getDefaultCipherSuites() {
return sslSocketFactory.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return sslSocketFactory.getSupportedCipherSuites();
}
@Override
public int hashCode() {
return sslSocketFactory.hashCode();
}
@Override
public String toString() {
return sslSocketFactory.toString();
}
@Override
public SocketImpl createSocketImpl() {
return null;
}
/**
* Loads the encryption mechanism
* @param keystoreFile the keystore file
* @param password the password for the keystore
* @throws KeyStoreException if KeyManagerFactory.init or TrustManagerFactory.init fails
* @throws NoSuchAlgorithmException if the algorithm for the keystore or key manager could not be found
* @throws CertificateException if any of the certificates in the keystore could not be loaded
* @throws FileNotFoundException if the keystore file does not exist
* @throws IOException if there is an I/O or format problem with the keystore data, if a password is required but not given, or if the given password was incorrect. If the error is due to a wrong password, the cause of the IOException should be an UnrecoverableKeyException
* @throws KeyManagementException if SSLContext.init fails
* @throws UnrecoverableKeyException if the key cannot be recovered (e.g. the given password is wrong).
*/
public void load(File keystoreFile, char [] password) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, KeyManagementException, UnrecoverableKeyException {
// First initialize the key and trust material
KeyStore ksKeys = KeyStore.getInstance("JKS");
ksKeys.load(new FileInputStream(keystoreFile), password);
// KeyManagers decide which key material to use
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ksKeys, password);
// TrustManagers decide whether to allow connections
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(ksKeys);
// Used to create the
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
this.sslSocketFactory = sslContext.getSocketFactory();
loaded.set(true);
}
public boolean isLoaded() {
return loaded.get();
}
@Override
public Socket createSocket() throws IOException {
if (!loaded.get())
throw new IllegalStateException("SecureSocketFactory hasn't been loaded yet!");
return sslSocketFactory.createSocket();
}
}

View File

@@ -0,0 +1,44 @@
package com.projectswg.common.network;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
public class TCPSecureSocket extends TCPSocket {
private final SecureSocketFactory socketFactory;
public TCPSecureSocket(InetSocketAddress address, int bufferSize) {
super(address, bufferSize);
this.socketFactory = new SecureSocketFactory();
}
/**
* Sets up the encryption mechanism
* @param keystoreFile the keystore file
* @param password the password for the keystore
* @throws KeyStoreException if KeyManagerFactory.init or TrustManagerFactory.init fails
* @throws NoSuchAlgorithmException if the algorithm for the keystore or key manager could not be found
* @throws CertificateException if any of the certificates in the keystore could not be loaded
* @throws FileNotFoundException if the keystore file does not exist
* @throws IOException if there is an I/O or format problem with the keystore data, if a password is required but not given, or if the given password was incorrect. If the error is due to a wrong password, the cause of the IOException should be an UnrecoverableKeyException
* @throws KeyManagementException if SSLContext.init fails
* @throws UnrecoverableKeyException if the key cannot be recovered (e.g. the given password is wrong).
*/
public void setupEncryption(File keystoreFile, char [] password) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, KeyManagementException, UnrecoverableKeyException {
socketFactory.load(keystoreFile, password);
}
@Override
public Socket createSocket() throws IOException {
return socketFactory.createSocket();
}
}

View File

@@ -1,77 +1,82 @@
/***********************************************************************************
* Copyright (c) 2015 /// Project SWG /// www.projectswg.com *
* *
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
* Our goal is to create an emulator which will provide a server for players to *
* continue playing a game similar to the one they used to play. We are basing *
* it on the final publish of the game prior to end-game events. *
* *
* This file is part of Holocore. *
* *
* -------------------------------------------------------------------------------- *
* *
* Holocore is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* Holocore is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
* Copyright (c) 2017 /// Project SWG /// www.projectswg.com *
* *
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
* Our goal is to create an emulator which will provide a server for players to *
* continue playing a game similar to the one they used to play. We are basing *
* it on the final publish of the game prior to end-game events. *
* *
* This file is part of Holocore. *
* *
* --------------------------------------------------------------------------------*
* *
* Holocore is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* Holocore is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
package com.projectswg.common.network;
import com.projectswg.common.concurrency.PswgBasicThread;
import com.projectswg.common.concurrency.PswgThreadPool;
import com.projectswg.common.debug.Log;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedByInterruptException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Locale;
import java.nio.channels.*;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;
import com.projectswg.common.callback.CallbackManager;
import com.projectswg.common.debug.Assert;
import com.projectswg.common.debug.Log;
public class TCPServer {
public class TCPServer<T extends TCPServer.TCPSession> {
private final CallbackManager<TCPCallback> callbackManager;
private final Map<SocketAddress, SocketChannel> sockets;
private final PswgThreadPool callbackThread;
private final Map<SocketChannel, T> channels;
private final Map<Long, T> sessionIdToChannel;
private final PswgBasicThread listener;
private final AtomicBoolean running;
private final InetAddress addr;
private final int port;
private final int bufferSize;
private final TCPServerListener listener;
private final InetSocketAddress addr;
private final Function<SocketChannel, T> sessionCreator;
private ServerSocketChannel channel;
public TCPServer(int port, int bufferSize) {
this(null, port, bufferSize);
private final ByteBuffer buffer;
private final ByteArrayOutputStream bufferStream;
private final WritableByteChannel byteBufferChannel;
public TCPServer(int port, int bufferSize, Function<SocketChannel, T> sessionCreator) {
this(new InetSocketAddress((InetAddress) null, port), bufferSize, sessionCreator);
}
public TCPServer(InetAddress addr, int port, int bufferSize) {
this.callbackManager = new CallbackManager<>("tcpserver-"+port, 1);
this.sockets = new HashMap<>();
public TCPServer(InetSocketAddress addr, int bufferSize, Function<SocketChannel, T> sessionCreator) {
this.callbackThread = new PswgThreadPool(false, 1, "tcpserver-" + addr.getPort());
this.channels = new ConcurrentHashMap<>();
this.sessionIdToChannel = new ConcurrentHashMap<>();
this.listener = new PswgBasicThread("tcpserver-listener-" + addr.getPort(), this::runListener);
this.running = new AtomicBoolean(false);
this.addr = addr;
this.port = port;
this.bufferSize = bufferSize;
this.channel = null;
listener = new TCPServerListener();
this.sessionCreator = sessionCreator;
this.buffer = ByteBuffer.allocateDirect(bufferSize);
this.bufferStream = new ByteArrayOutputStream(bufferSize);
this.byteBufferChannel = Channels.newChannel(bufferStream);
}
public int getPort() {
@@ -79,190 +84,222 @@ public class TCPServer {
}
public void bind() throws IOException {
if (running.getAndSet(true)) {
Assert.fail();
assert !running.get() : "TCPServer is already running";
if (running.getAndSet(true))
return;
}
callbackManager.start();
callbackThread.start();
channel = ServerSocketChannel.open();
channel.socket().bind(new InetSocketAddress(addr, port));
channel.bind(addr, 50);
channel.configureBlocking(false);
listener.start();
}
public boolean disconnect(SocketAddress sock) {
Assert.notNull(sock);
synchronized (sockets) {
SocketChannel sc = sockets.get(sock);
if (sc == null)
return true;
sockets.remove(sock);
try {
sc.close();
callbackManager.callOnEach((callback) -> callback.onConnectionDisconnect(sc, sock));
return true;
} catch (IOException e) {
Log.e(e);
return false;
public void disconnect(long sessionId) {
T session = sessionIdToChannel.remove(sessionId);
if (session == null) {
Log.w("TCPServer - unknown session id in disconnect: %d", sessionId);
return;
}
disconnect(session.getChannel());
}
public void disconnect(T session) {
disconnect(Objects.requireNonNull(session, "session").getChannel());
}
public void disconnect(SocketChannel sc) {
T session = channels.remove(sc);
if (session == null) {
Log.w("TCPServer - unknown channel in disconnect: %d", sc);
return;
}
sessionIdToChannel.remove(session.getSessionId());
session.close();
callbackThread.execute(session::onDisconnected);
}
public T getSession(long sessionId) {
return sessionIdToChannel.get(sessionId);
}
public T getSession(SocketChannel sc) {
return channels.get(sc);
}
public void close() {
assert running.get() : "TCPServer isn't running";
if (!running.getAndSet(false))
return;
callbackThread.stop(false);
listener.stop(true);
safeClose(channel);
}
private void runListener() {
try (Selector selector = Selector.open()) {
channel.register(selector, SelectionKey.OP_ACCEPT);
while (running.get()) {
selector.select();
accept(selector);
selector.selectedKeys().forEach(this::read);
}
}
}
private boolean disconnect(SocketChannel sc) {
try {
return disconnect(sc.getRemoteAddress());
} catch (IOException e) {
return false;
}
}
public boolean close() {
if (!running.getAndSet(false)) {
Assert.fail();
return false;
}
callbackManager.stop();
listener.stop();
try {
channel.close();
return true;
} catch (IOException e) {
Log.e(e);
}
return false;
}
public SocketChannel getChannel(SocketAddress sock) {
synchronized (sockets) {
return sockets.get(sock);
}
}
public void setCallback(TCPCallback callback) {
callbackManager.setCallback(callback);
}
public interface TCPCallback {
void onIncomingConnection(SocketChannel s, SocketAddress addr);
void onConnectionDisconnect(SocketChannel s, SocketAddress addr);
void onIncomingData(SocketChannel s, SocketAddress addr, byte [] data);
}
private class TCPServerListener implements Runnable {
private final ByteBuffer buffer;
private Thread thread;
private boolean running;
public TCPServerListener() {
buffer = ByteBuffer.allocateDirect(bufferSize);
running = false;
thread = null;
}
public void start() {
running = true;
thread = new Thread(this, "TCPServerListener-" + channel.socket().getLocalPort());
thread.start();
}
public void stop() {
running = false;
if (thread != null)
thread.interrupt();
thread = null;
}
@Override
public void run() {
try (Selector selector = setupSelector()) {
while (running) {
try {
selector.select();
processSelectionKeys(selector);
} catch (Exception e) {
Log.e(e);
try {
Thread.sleep(100);
} catch (InterruptedException e1) {
break;
}
}
}
} catch (IOException e) {
Log.e(e);
private void accept(Selector selector) {
try {
while (channel.isOpen()) {
SocketChannel sc = channel.accept();
if (sc == null)
return;
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ);
acceptConnection(sc);
}
} catch (ClosedChannelException e) {
// Ignored
} catch (Throwable t) {
Log.w("TCPServer - IOException in accept(): %s", t.getMessage());
}
private Selector setupSelector() throws IOException {
Selector selector = Selector.open();
channel.register(selector, SelectionKey.OP_ACCEPT);
return selector;
}
private void acceptConnection(SocketChannel sc) {
T session = sessionCreator.apply(sc);
if (session == null) {
Log.w("Session creator for TCPServer-%d created a null session!", addr.getPort());
safeClose(sc);
return;
}
private void processSelectionKeys(Selector selector) throws ClosedChannelException {
for (SelectionKey key : selector.selectedKeys()) {
if (!key.isValid())
continue;
if (key.isAcceptable()) {
accept(selector);
} else if (key.isReadable()) {
SelectableChannel selectable = key.channel();
if (selectable instanceof SocketChannel) {
boolean canRead = true;
while (canRead)
canRead = read(key, (SocketChannel) selectable);
}
}
}
if (session.getChannel() != sc) {
Log.w("Session creator for TCPServer-%d created a session with an invalid channel!", addr.getPort());
safeClose(sc);
return;
}
private void accept(Selector selector) {
try {
while (channel.isOpen()) {
SocketChannel sc = channel.accept();
if (sc == null)
break;
SocketChannel old = sockets.get(sc.getRemoteAddress());
if (old != null)
disconnect(old);
sockets.put(sc.getRemoteAddress(), sc);
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ);
SocketAddress addr = sc.getRemoteAddress();
callbackManager.callOnEach((callback) -> callback.onIncomingConnection(sc, addr));
}
} catch (IOException e) {
Log.e(e);
}
channels.put(sc, session);
sessionIdToChannel.put(session.getSessionId(), session);
callbackThread.execute(session::onConnected);
}
private void read(SelectionKey key) {
SelectableChannel selectableChannel = key.channel();
if (selectableChannel == channel)
return;
SocketChannel sc = (SocketChannel) selectableChannel;
T session = getSession(sc);
if (session == null || !sc.isConnected()) {
invalidate(sc, key);
return;
}
private boolean read(SelectionKey key, SocketChannel s) {
try {
buffer.position(0);
buffer.limit(bufferSize);
int n = s.read(buffer);
try {
bufferStream.reset();
int n = 1;
while (n > 0) {
buffer.clear();
n = sc.read(buffer);
buffer.flip();
if (n < 0) {
key.cancel();
disconnect(s);
} else if (n > 0) {
ByteBuffer smaller = ByteBuffer.allocate(n);
smaller.put(buffer);
SocketAddress addr = s.getRemoteAddress();
callbackManager.callOnEach((callback) -> callback.onIncomingData(s, addr, smaller.array()));
return true;
}
} catch (IOException e) {
if (e.getMessage() != null && e.getMessage().toLowerCase(Locale.US).contains("connection reset"))
Log.e("Connection Reset with %s", s.socket().getRemoteSocketAddress());
else if (!(e instanceof ClosedByInterruptException))
Log.e(e);
key.cancel();
disconnect(s);
byteBufferChannel.write(buffer);
}
return false;
if (bufferStream.size() > 0) {
byte[] data = bufferStream.toByteArray();
callbackThread.execute(() -> session.onIncomingData(data));
}
if (n < 0) {
invalidate(sc, key);
}
} catch (ClosedChannelException e) {
// Ignored
} catch (Throwable t) {
Log.w("TCPServer - IOException in read(): %s", t.getMessage());
invalidate(sc, key);
}
}
private void invalidate(SocketChannel sc, SelectionKey key) {
key.cancel();
disconnect(sc);
}
private static void safeClose(Channel c) {
try {
c.close();
} catch (IOException e) {
// Ignored - as long as it's closed
}
}
public abstract static class TCPSession {
private static final AtomicLong GLOBAL_SESSION_ID = new AtomicLong(0);
private final SocketChannel sc;
private final SocketAddress addr;
private final long sessionId;
protected TCPSession(SocketChannel sc) {
this.sc = Objects.requireNonNull(sc, "socket");
this.sessionId = GLOBAL_SESSION_ID.incrementAndGet();
SocketAddress addr;
try {
addr = sc.getRemoteAddress();
} catch (IOException e) {
addr = null;
}
this.addr = addr;
}
protected void onConnected() {
}
protected void onDisconnected() {
}
/**
* Returns a globally unique session id for this particular connection
* @return the unique session id
*/
protected final long getSessionId() {
return sessionId;
}
/**
* Returns the socket channel associated with this session
* @return the socket channel
*/
protected final SocketChannel getChannel() {
return sc;
}
/**
* Returns the remote address that this socket is/was connected to
* @return the remote socket address
*/
@SuppressWarnings("unused") // open for subclass to use
protected final SocketAddress getRemoteAddress() {
return addr;
}
@SuppressWarnings("unused") // open for subclass to use
protected void writeToChannel(ByteBuffer data) throws IOException {
sc.write(data);
}
@SuppressWarnings("unused") // open for subclass to use
protected void writeToChannel(byte [] data) throws IOException {
sc.write(ByteBuffer.wrap(data));
}
protected void close() {
safeClose(sc);
}
protected abstract void onIncomingData(byte[] data);
}
}

View File

@@ -29,10 +29,13 @@ package com.projectswg.common.network;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import com.projectswg.common.callback.CallbackManager;
import com.projectswg.common.concurrency.Delay;
@@ -43,16 +46,33 @@ public class TCPSocket {
private final CallbackManager<TCPSocketCallback> callbackManager;
private final TCPSocketListener listener;
private final InetSocketAddress address;
private final AtomicReference<InetSocketAddress> address;
private final AtomicReference<SocketState> state;
private final Object stateMutex;
private final int bufferSize;
private SocketChannel socket;
private Socket socket;
private InputStream socketInputStream;
private OutputStream socketOutputStream;
public TCPSocket(InetSocketAddress address, int bufferSize) {
this.callbackManager = new CallbackManager<>("tcpsocket-"+address, 1);
this.listener = new TCPSocketListener();
this.address = address;
this.address = new AtomicReference<>(address);
this.state = new AtomicReference<>(SocketState.CLOSED);
this.stateMutex = new Object();
this.bufferSize = bufferSize;
this.socket = null;
this.socketInputStream = null;
this.socketOutputStream = null;
}
public TCPSocket(int bufferSize) {
this(null, bufferSize);
}
public TCPSocket() {
this(1024);
}
public int getBufferSize() {
@@ -60,19 +80,27 @@ public class TCPSocket {
}
public InetSocketAddress getRemoteAddress() {
return address;
return address.get();
}
public SocketChannel getSocket() {
public void setRemoteAddress(InetSocketAddress address) {
this.address.set(address);
}
public Socket getSocket() {
return socket;
}
public boolean isAlive() {
return socket != null && listener.isAlive();
synchronized (stateLock()) {
return socket != null && listener.isAlive();
}
}
public boolean isConnected() {
return socket != null && socket.isConnected();
synchronized (stateLock()) {
return socket != null && socket.isConnected();
}
}
public void setCallback(TCPSocketCallback callback) {
@@ -83,57 +111,114 @@ public class TCPSocket {
callbackManager.clearCallbacks();
}
public boolean connect() {
Assert.isNull(socket, "Socket must be null! Cannot connect twice!");
Assert.test(!listener.isAlive(), "Listener must not be alive! Cannot connect twice!");
try {
callbackManager.start();
socket = SocketChannel.open(address);
if (socket.finishConnect()) {
listener.start();
callbackManager.callOnEach((callback) -> callback.onConnected(this));
return true;
}
} catch (IOException e) {
Log.e(e);
public void createConnection() throws IOException {
synchronized (stateLock()) {
checkAndSetState(SocketState.CLOSED, SocketState.CREATED);
socket = createSocket();
}
socket = null;
callbackManager.stop();
return false;
}
public void startConnection() throws IOException {
synchronized (stateLock()) {
try {
checkAndSetState(SocketState.CREATED, SocketState.CONNECTING);
socket.connect(getRemoteAddress());
socketInputStream = socket.getInputStream();
socketOutputStream = socket.getOutputStream();
} catch (IOException e) {
checkAndSetState(SocketState.CONNECTING, SocketState.CLOSED);
socket = null;
socketInputStream = null;
socketOutputStream = null;
throw e;
}
callbackManager.start();
listener.start();
checkAndSetState(SocketState.CONNECTING, SocketState.CONNECTED);
callbackManager.callOnEach((callback) -> callback.onConnected(this));
}
}
public void connect() throws IOException {
createConnection();
startConnection();
}
public boolean disconnect() {
if (socket == null)
return true;
try {
socket.close();
if (listener.isAlive()) {
listener.stop();
listener.awaitTermination();
synchronized (stateLock()) {
if (socket == null)
return true;
try {
checkAndSetState(SocketState.CONNECTED, SocketState.CLOSED);
socket.close();
socket = null;
socketInputStream = null;
socketOutputStream = null;
if (listener.isAlive()) {
listener.stop();
listener.awaitTermination();
}
if (callbackManager.isRunning()) {
callbackManager.callOnEach((callback) -> callback.onDisconnected(this));
callbackManager.stop();
}
return true;
} catch (IOException e) {
Log.e(e);
}
if (callbackManager.isRunning()) {
callbackManager.callOnEach((callback) -> callback.onDisconnected(this));
callbackManager.stop();
}
socket = null;
return true;
} catch (IOException e) {
Log.e(e);
return false;
}
return false;
}
public boolean send(NetBuffer data) {
return send(data.array(), data.position(), data.remaining());
}
public boolean send(ByteBuffer data) {
try {
while (data.hasRemaining()) {
if (socket == null || socket.write(data) <= 0)
return send(data.array(), data.position(), data.remaining());
}
public boolean send(byte [] data) {
return send(data, 0, data.length);
}
public boolean send(byte [] data, int offset, int length) {
synchronized (stateLock()) {
try {
if (socket == null)
return false;
if (length > 0)
socketOutputStream.write(data, offset, length);
return true;
} catch (IOException e) {
Log.e(e);
}
return true;
} catch (IOException e) {
Log.e(e);
return false;
}
return false;
}
protected Socket createSocket() throws IOException {
return new Socket();
}
protected final Object stateLock() {
return stateMutex;
}
/**
* Checks the current state to see if it matches the expected, and if so, changes it to the new state. If not, it fails the assertion
* @param expected the expected state
* @param state the new state
*/
private void checkAndSetState(SocketState expected, SocketState state) {
Assert.notNull(expected, "Expected state cannot be null!");
Assert.notNull(state, "New state cannot be null!");
Assert.test(this.state.compareAndSet(expected, state), "Failed to set state! Was: " + this.state.get() + " Expected: " + expected + " Update: " + state);
}
public interface TCPSocketCallback {
@@ -142,6 +227,13 @@ public class TCPSocket {
void onIncomingData(TCPSocket socket, byte [] data);
}
private enum SocketState {
CLOSED,
CREATED,
CONNECTING,
CONNECTED
}
private class TCPSocketListener implements Runnable {
private final AtomicBoolean running;
@@ -158,7 +250,7 @@ public class TCPSocket {
public void start() {
Assert.test(!running.get(), "Cannot start listener! Already started!");
Assert.isNull(thread, "Cannot start listener! Already started!");
thread = new Thread(this);
thread = new Thread(this, "TCPServer Port#" + getRemoteAddress().getPort());
running.set(true);
thread.start();
}
@@ -187,13 +279,10 @@ public class TCPSocket {
public void run() {
try {
alive.set(true);
SocketChannel sc = TCPSocket.this.socket;
int bufferSize = TCPSocket.this.bufferSize;
Assert.notNull(sc, "SocketChannel is null at start of listener run()!");
Assert.test(bufferSize > 0, "Buffer size is <= 0 at start of listener run()!");
ByteBuffer buf = ByteBuffer.allocateDirect(bufferSize);
InputStream input = TCPSocket.this.socketInputStream;
byte [] buffer = new byte[TCPSocket.this.bufferSize];
while (running.get()) {
waitIncoming(sc, buf, bufferSize);
waitIncoming(input, buffer);
}
} catch (Throwable t) {
@@ -205,18 +294,14 @@ public class TCPSocket {
}
}
private void waitIncoming(SocketChannel sc, ByteBuffer buf, int bufferSize) throws IOException {
buf.position(0);
buf.limit(bufferSize);
int n = sc.read(buf);
buf.flip();
private void waitIncoming(InputStream input, byte [] buffer) throws IOException {
int n = input.read(buffer);
if (n == 0)
return;
if (n < 0)
throw new EOFException();
byte [] data = new byte[n];
buf.position(0);
buf.get(data, 0, n);
System.arraycopy(buffer, 0, data, 0, n);
callbackManager.callOnEach((callback) -> callback.onIncomingData(TCPSocket.this, data));
}

View File

@@ -370,7 +370,7 @@ public enum PacketType {
return null;
Class <? extends SWGPacket> c = type.c;
try {
return c.newInstance();
return c.getConstructor().newInstance();
} catch (Exception e) {
Log.e("Packet: [%08X] %s", crc, c.getName());
Log.e(e);

View File

@@ -0,0 +1,94 @@
package com.projectswg.common.process;
import com.projectswg.common.debug.Log;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;
public class ArgumentParser {
public static Map<String, String> parseArguments(String [] args, Function<String, String> renameArgument, Predicate<String>expectsArgument) {
Map<String, String> arguments = new HashMap<>();
for (int i = 0; i < args.length; i++) {
String arg = args[i];
int equalIndex = arg.indexOf('=');
String key = arg;
String value = null;
if (equalIndex != -1)
key = arg.substring(0, equalIndex);
key = renameArgument.apply(key);
if (expectsArgument.test(key)) {
if (equalIndex == -1) { // seek ahead for argument
if (i + 1 < args.length)
value = args[i+1];
} else { // parse out argument
value = arg.substring(equalIndex+1);
}
}
arguments.put(key, value);
}
return arguments;
}
public static void requireValue(Map<String, String> arguments, String key) {
if (!arguments.containsKey(key) || arguments.get(key) == null) {
throw new IllegalArgumentException(key + " - no argument");
}
}
public static void requireShort(Map<String, String> arguments, String key) {
requireValue(arguments, key);
try {
Short.parseShort(arguments.get(key));
} catch (NumberFormatException e) {
throw new IllegalArgumentException(key + " - invalid argument");
}
}
public static void requireInt(Map<String, String> arguments, String key) {
requireValue(arguments, key);
try {
Integer.parseInt(arguments.get(key));
} catch (NumberFormatException e) {
throw new IllegalArgumentException(key + " - invalid argument");
}
}
public static void requireLong(Map<String, String> arguments, String key) {
requireValue(arguments, key);
try {
Long.parseLong(arguments.get(key));
} catch (NumberFormatException e) {
throw new IllegalArgumentException(key + " - invalid argument");
}
}
public static int parseShort(String str, int defaultValue) {
try {
return Short.parseShort(str);
} catch (NumberFormatException e) {
return defaultValue;
}
}
public static int parseInt(String str, int defaultValue) {
try {
return Integer.parseInt(str);
} catch (NumberFormatException e) {
return defaultValue;
}
}
public static long parseLong(String str, long defaultValue) {
try {
return Long.parseLong(str);
} catch (NumberFormatException e) {
return defaultValue;
}
}
}

View File

@@ -0,0 +1,261 @@
package com.projectswg.common.process;
import com.projectswg.common.debug.Assert;
import java.io.File;
import java.io.IOException;
import java.lang.ProcessBuilder.Redirect;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
public class JarProcessBuilder {
private final ProcessBuilder builder;
private final String javaPath;
private final String jarPath;
private final List<String> vmArguments;
private final List<String> arguments;
private long minMemory;
private long maxMemory;
public JarProcessBuilder(File java, File jar) {
this.builder = new ProcessBuilder();
String javaPath, jarPath;
try {
javaPath = java.getCanonicalPath();
jarPath = jar.getCanonicalPath();
} catch (IOException e) {
javaPath = java.getAbsolutePath();
jarPath = jar.getAbsolutePath();
}
this.javaPath = javaPath;
this.jarPath = jarPath;
this.vmArguments = new CopyOnWriteArrayList<>();
this.arguments = new CopyOnWriteArrayList<>();
this.minMemory = Long.MIN_VALUE;
this.maxMemory = Long.MIN_VALUE;
}
public JarProcessBuilder setMinMemory(long minMemory, MemoryUnit unit) {
Assert.test(unit == MemoryUnit.KILOBYTES || unit == MemoryUnit.MEGABYTES || unit == MemoryUnit.GIGABYTES, "Unsupported memory unit!");
this.minMemory = unit.getBytes(minMemory);
return this;
}
public JarProcessBuilder setMaxMemory(long maxMemory, MemoryUnit unit) {
Assert.test(unit == MemoryUnit.KILOBYTES || unit == MemoryUnit.MEGABYTES || unit == MemoryUnit.GIGABYTES, "Unsupported memory unit!");
this.maxMemory = unit.getBytes(maxMemory);
return this;
}
public JarProcessBuilder setMemory(long minMemory, long maxMemory, MemoryUnit unit) {
Assert.test(unit == MemoryUnit.KILOBYTES || unit == MemoryUnit.MEGABYTES || unit == MemoryUnit.GIGABYTES, "Unsupported memory unit!");
this.minMemory = unit.getBytes(minMemory);
this.maxMemory = unit.getBytes(maxMemory);
return this;
}
public JarProcessBuilder setVMArguments(String ... args) {
return setVMArguments(Arrays.asList(args));
}
public JarProcessBuilder setVMArguments(List<String> args) {
this.vmArguments.clear();
this.vmArguments.addAll(args);
return this;
}
public JarProcessBuilder setArguments(String ... args) {
return setArguments(Arrays.asList(args));
}
public JarProcessBuilder setArguments(List<String> args) {
this.arguments.clear();
this.arguments.addAll(args);
return this;
}
public List<String> command() {
return builder.command();
}
public Map<String, String> environment() {
return builder.environment();
}
public File directory() {
return builder.directory();
}
public JarProcessBuilder directory(File directory) {
builder.directory(directory);
return this;
}
public JarProcessBuilder redirectInput(Redirect source) {
builder.redirectInput(source);
return this;
}
public JarProcessBuilder redirectOutput(Redirect destination) {
builder.redirectOutput(destination);
return this;
}
public JarProcessBuilder redirectError(Redirect destination) {
builder.redirectError(destination);
return this;
}
public JarProcessBuilder redirectInput(File file) {
builder.redirectInput(file);
return this;
}
public JarProcessBuilder redirectOutput(File file) {
builder.redirectOutput(file);
return this;
}
public JarProcessBuilder redirectError(File file) {
builder.redirectError(file);
return this;
}
public Redirect redirectInput() {
return builder.redirectInput();
}
public Redirect redirectOutput() {
return builder.redirectOutput();
}
public Redirect redirectError() {
return builder.redirectError();
}
public JarProcessBuilder inheritIO() {
builder.inheritIO();
return this;
}
public boolean redirectErrorStream() {
return builder.redirectErrorStream();
}
public JarProcessBuilder redirectErrorStream(boolean redirectErrorStream) {
builder.redirectErrorStream(redirectErrorStream);
return this;
}
public Process start() throws IOException {
return builder.command(createCommand()).start();
}
@Override
public int hashCode() {
return builder.hashCode();
}
@Override
public boolean equals(Object obj) {
return builder.equals(obj);
}
@Override
public String toString() {
return builder.toString();
}
private List<String> createCommand() {
List<String> command = new ArrayList<>();
command.add(javaPath);
if (minMemory > 0) {
MemoryUnit unit = MemoryUnit.getAppropriateUnit(minMemory);
command.add("-Xms" + minMemory/unit.getFactor() + getJavaChar(unit));
}
if (maxMemory > 0) {
MemoryUnit unit = MemoryUnit.getAppropriateUnit(maxMemory);
command.add("-Xmx" + maxMemory/unit.getFactor() + getJavaChar(unit));
}
command.addAll(vmArguments);
command.add("-jar");
command.add(jarPath);
command.addAll(arguments);
return command;
}
private static char getJavaChar(MemoryUnit unit) {
switch (unit) {
case BYTES:
return 'b';
case KILOBYTES:
default:
return 'k';
case MEGABYTES:
return 'm';
case GIGABYTES:
return 'g';
case TERABYTES:
return 't';
}
}
public static File getJavaPath() {
return new File(System.getProperty("java.home"), "bin/java");
}
public enum MemoryUnit {
BYTES ("B", 1),
KILOBYTES ("kB", 1024),
MEGABYTES ("MB", 1048576),
GIGABYTES ("GB", 1073741824),
TERABYTES ("TB", 1099511627776L);
private final String suffix;
private final long factor;
MemoryUnit(String suffix, long factor) {
this.suffix = suffix;
this.factor = factor;
}
public String getSuffix() {
return suffix;
}
public long getFactor() {
return factor;
}
public long getBytes(double val) {
return (long) (val * factor);
}
public long getBytes(long val) {
return val * factor;
}
public double convert(double val, MemoryUnit unit) {
long bytes = getBytes(val);
return bytes / (double) unit.getFactor();
}
public static MemoryUnit getAppropriateUnit(long val) {
if (val < KILOBYTES.getFactor())
return MemoryUnit.BYTES;
if (val < MEGABYTES.getFactor())
return MemoryUnit.KILOBYTES;
if (val < GIGABYTES.getFactor())
return MemoryUnit.MEGABYTES;
if (val < TERABYTES.getFactor())
return MemoryUnit.GIGABYTES;
return MemoryUnit.TERABYTES;
}
}
}

View File

@@ -0,0 +1,50 @@
package com.projectswg.common.utilities;
import java.io.File;
import java.util.concurrent.atomic.AtomicReference;
public class LocalUtilities {
private static final AtomicReference<String> APP_NAME = new AtomicReference<>(".projectswg");
public static void setApplicationName(String name) {
APP_NAME.set(name);
}
public static File getSubApplicationDirectory(String ... names) {
File dir = getApplicationDirectory();
for (String name : names) {
dir = new File(dir, name);
if (!dir.exists())
dir.mkdir();
}
return dir;
}
public static File getApplicationDirectory() {
String home = getHomeDirectory();
if (home == null)
throw new IllegalStateException("Unknown home directory for OS '"+System.getProperty("os.name")+"'");
File dir = new File(home, APP_NAME.get());
if (!dir.exists())
dir.mkdirs();
return dir;
}
private static String getHomeDirectory() {
String os = System.getProperty("os.name").toUpperCase();
if (os.contains("WIN"))
return System.getenv("APPDATA");
if (os.contains("MAC"))
return System.getProperty("user.home") + "/Library/Application Support";
if (os.contains("NUX"))
return System.getProperty("user.home");
return System.getProperty("user.dir");
}
}