Improved network efficiency

This commit is contained in:
Obique PSWG
2017-11-20 00:09:41 -06:00
parent a0931a2e4e
commit 1e55fbef28
15 changed files with 431 additions and 604 deletions

View File

@@ -1,11 +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/">
<accessrules>
<accessrule kind="accessible" pattern="javafx/**"/>
</accessrules>
</classpathentry>
<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.buildship.core.gradleclasspathcontainer"/>
<classpathentry kind="output" path="bin"/>
</classpath>

2
.gitignore vendored
View File

@@ -1 +1,3 @@
bin/
/.gradle/
/build/

Binary file not shown.

View File

@@ -8,6 +8,7 @@ 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;
@@ -20,8 +21,6 @@ import com.projectswg.networking.server.ServerConnectionService;
import com.projectswg.networking.soe.Disconnect.DisconnectReason;
import com.projectswg.services.PacketRecordingService;
import network.packets.swg.ErrorMessage;
public class Connections extends Manager {
public static final String VERSION = "0.9.8";

View File

@@ -1,11 +1,14 @@
package com.projectswg.networking;
import com.projectswg.common.network.NetBuffer;
import com.projectswg.networking.client.ClientData;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import network.packets.swg.login.LoginClientId;
import network.packets.swg.login.LoginClusterStatus;
import resources.Galaxy;
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 {
@@ -24,12 +27,12 @@ public class NetInterceptor {
public byte [] interceptClient(byte [] data) {
if (data.length < 6)
return data;
NetBuffer buffer = NetBuffer.wrap(data);
buffer.getShort();
switch (buffer.getInt()) {
case 0x41131F96: // LoginClientId
return setAutoLogin(buffer);
case 0x43FD1C22: // CmdSceneReady
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:
@@ -40,11 +43,11 @@ public class NetInterceptor {
public byte [] interceptServer(byte [] data) {
if (data.length < 6)
return data;
NetBuffer buffer = NetBuffer.wrap(data);
buffer.getShort();
switch (buffer.getInt()) {
case 0x3436AEB6: // LoginClusterStatus
return getServerList(buffer);
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;
}

View File

@@ -4,6 +4,8 @@ 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;
@@ -15,9 +17,6 @@ import com.projectswg.networking.soe.Disconnect;
import com.projectswg.networking.soe.Disconnect.DisconnectReason;
import com.projectswg.resources.ClientConnectionStatus;
import network.packets.swg.SWGPacket;
import com.projectswg.common.network.packets.swg.zone.HeartBeat;
public class ClientConnectionService extends Manager implements ClientPacketSender {
private final NetInterceptor interceptor;

View File

@@ -1,9 +1,8 @@
package com.projectswg.networking.client;
import com.projectswg.common.network.packets.SWGPacket;
import com.projectswg.networking.Packet;
import network.packets.swg.SWGPacket;
public interface ClientPacketSender {
/** Adds the specified packets to a buffer to guarantee sending in-order */

View File

@@ -3,11 +3,14 @@ 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.concurrency.SmartLock;
import com.projectswg.common.concurrency.SynchronizedQueue;
import com.projectswg.common.control.IntentManager;
import com.projectswg.common.debug.Assert;
import com.projectswg.intents.ClientConnectionChangedIntent;
@@ -21,133 +24,196 @@ import com.projectswg.resources.ClientConnectionStatus;
public class ClientPackager {
private final NetInterceptor interceptor;
private final ClientData clientData;
private final ClientPacketResender packetResender;
private final PswgBasicThread packagerThread;
private final Queue<byte []> inboundQueue;
private final SmartLock inboundQueueLock;
private final DataChannel channel;
private int size;
private final PackagingWrapper packager;
public ClientPackager(NetInterceptor interceptor, ClientData clientData, ClientPacketSender sender) {
this.interceptor = interceptor;
this.clientData = clientData;
this.inboundQueue = new SynchronizedQueue<>(new ArrayDeque<>(128));
this.inboundQueueLock = new SmartLock();
this.packagerThread = new PswgBasicThread("packet-packager", () -> packagerRunnable());
this.packetResender = new ClientPacketResender(sender);
this.channel = new DataChannel();
reset();
this.packager = new PackagingWrapper(packetResender, clientData);
}
public void start(IntentManager intentManager) {
Assert.test(inboundQueue.isEmpty(), "Inbound queue must be empty when starting!");
packagerThread.start();
packetResender.start(intentManager);
packager.start();
intentManager.registerForIntent(ClientConnectionChangedIntent.class, ccci -> handleClientStatusChanged(ccci.getStatus()));
}
public void stop() {
packager.stop();
packetResender.stop();
packagerThread.stop(true);
packagerThread.awaitTermination(1000);
inboundQueue.clear();
}
public void clear() {
inboundQueue.clear();
packager.reset();
}
public void addToPackage(byte [] data) {
Assert.test(data.length > 0, "Array length must be greater than 0!");
inboundQueue.add(data);
inboundQueueLock.signal();
packager.add(interceptor.interceptServer(data));
}
private void handleClientStatusChanged(ClientConnectionStatus status) {
clear();
}
private void packagerRunnable() {
while (packagerThread.isRunning()) {
if (handle(inboundQueue) > 0) {
if (Delay.sleepMicro(25))
private static class PackagingWrapper {
private final AtomicBoolean running;
private final PswgBasicThread packagerThread;
private final ClientPacketResender packetResender;
private final Packager packager;
private final Queue<byte []> inboundQueue;
private final Lock inboundQueueLock;
private final Condition inboundQueueCondition;
public PackagingWrapper(ClientPacketResender packetResender, ClientData clientData) {
this.running = new AtomicBoolean(false);
this.packagerThread = new PswgBasicThread("packet-packager", this::loop);
this.packetResender = packetResender;
this.packager = new Packager(clientData);
this.inboundQueue = new ArrayDeque<>(128);
this.inboundQueueLock = new ReentrantLock(false);
this.inboundQueueCondition = inboundQueueLock.newCondition();
}
public void start() {
running.set(true);
packagerThread.start();
}
public void stop() {
running.set(false);
packagerThread.stop(true);
packagerThread.awaitTermination(500);
}
public void reset() {
inboundQueue.clear();
}
public void add(byte [] packet) {
inboundQueueLock.lock();
try {
inboundQueue.add(packet);
inboundQueueCondition.signal();
} finally {
inboundQueueLock.unlock();
}
}
private void loop() {
Queue<SequencedOutbound> outboundQueue = new ArrayDeque<>(64);
while (running.get()) {
if (!waitForInbound())
break;
} else {
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 {
while (inboundQueue.isEmpty()) {
inboundQueueLock.await();
}
inboundQueueCondition.await();
} catch (InterruptedException e) {
running.set(false);
return false;
}
}
return true;
}
}
/**
* Packages all packets in the queue appropriately
* @param queue the queue of raw packets
*/
private synchronized int handle(Queue<byte []> queue) {
byte [] packet;
int packetSize;
int packets = 0;
Assert.test(size == 8, "Internal Packager size must equal 8 at start of loop!");
Assert.test(channel.getPacketCount() == 0, "Internal Packager DataChannel must be empty at start of loop!");
while (!queue.isEmpty()) {
packet = interceptor.interceptServer(queue.poll());
packetSize = getPacketLength(packet);
if (size + packetSize >= 496) {
handleDataChannelOverflow(packet, packetSize);
} else {
addToChannel(packet, packetSize);
private void processInbound(Queue<SequencedOutbound> outboundQueue) {
try {
packager.handle(inboundQueue, outboundQueue);
} finally {
inboundQueueLock.unlock();
}
while (!outboundQueue.isEmpty()) {
packetResender.add(outboundQueue.poll());
}
packets++;
}
sendDataChannel();
return packets;
}
private void handleDataChannelOverflow(byte [] packet, int packetSize) {
sendDataChannel();
if (packetSize >= 496) {
sendFragmented(packet);
return;
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;
}
addToChannel(packet, packetSize);
}
private void addToChannel(byte [] packet, int packetSize) {
channel.addPacket(packet);
size += packetSize;
}
private void sendDataChannel() {
if (channel.getPacketCount() == 0)
return;
channel.setSequence(clientData.getAndIncrementTxSequence());
packetResender.add(channel.getSequence(), channel.encode().array());
reset();
}
private void sendFragmented(byte [] packet) {
Fragmented [] frags = Fragmented.encode(ByteBuffer.wrap(packet), clientData.getTxSequence());
clientData.setTxSequence((short) (clientData.getTxSequence() + frags.length));
for (Fragmented frag : frags) {
packetResender.add(frag.getSequence(), frag.encode().array());
/**
* Processes the inbound queue, and then sends it to the outbound queue
* @param inboundQueue inbound queue from the server
* @param outboundQueue outbound queue to the client
*/
public void handle(Queue<byte []> inboundQueue, Queue<SequencedOutbound> outboundQueue) {
byte [] packet;
int packetSize;
while (!inboundQueue.isEmpty()) {
packet = inboundQueue.poll();
packetSize = getPacketLength(packet);
if (size.get() + packetSize >= 496) // overflowed previous packet
sendDataChannel(outboundQueue);
if (packetSize < 496) {
addToDataChannel(packet, packetSize);
} else {
sendFragmented(outboundQueue, packet);
}
}
sendDataChannel(outboundQueue);
}
}
private int getPacketLength(byte [] data) {
int len = data.length;
if (len >= 255)
return len + 3;
return len + 1;
}
private void reset() {
channel.clearPackets();
size = 8;
private void addToDataChannel(byte [] packet, int packetSize) {
channel.addPacket(packet);
size.getAndAdd(packetSize);
}
private void sendDataChannel(Queue<SequencedOutbound> outboundQueue) {
if (channel.getPacketCount() == 0)
return;
channel.setSequence(clientData.getAndIncrementTxSequence());
outboundQueue.add(new SequencedOutbound(channel.getSequence(), channel.encode().array()));
reset();
}
private void sendFragmented(Queue<SequencedOutbound> outboundQueue, byte [] packet) {
Fragmented [] frags = Fragmented.encode(ByteBuffer.wrap(packet), clientData.getTxSequence());
clientData.setTxSequence((short) (clientData.getTxSequence() + frags.length));
for (Fragmented frag : frags) {
outboundQueue.add(new SequencedOutbound(frag.getSequence(), frag.encode().array()));
}
}
private void reset() {
channel.clearPackets();
size.set(8);
}
private static int getPacketLength(byte [] data) {
int len = data.length;
if (len >= 255)
return len + 3;
return len + 1;
}
}
}

View File

@@ -1,8 +1,11 @@
package com.projectswg.networking.client.sender;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
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;
@@ -14,7 +17,6 @@ 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.networking.soe.SequencedPacket;
import com.projectswg.resources.ClientConnectionStatus;
/**
@@ -22,195 +24,229 @@ import com.projectswg.resources.ClientConnectionStatus;
*/
public class ClientPacketResender {
private final List<SequencedOutbound> sentPackets;
private final ClientPacketSender sender;
private final PswgBasicThread resender;
private final CongestionAvoidance congAvoidance;
private final AtomicLong lastSent;
private final ResenderWrapper resender;
private final PswgBasicThread executor;
private final AtomicBoolean running;
public ClientPacketResender(ClientPacketSender sender) {
this.sender = sender;
this.sentPackets = new ArrayList<>(128);
this.resender = new PswgBasicThread("packet-resender", () -> resendRunnable());
this.congAvoidance = new CongestionAvoidance();
this.lastSent = new AtomicLong(0);
this.resender = new ResenderWrapper(sender);
this.executor = new PswgBasicThread("packet-resender", this::resender);
this.running = new AtomicBoolean(false);
}
public void start(IntentManager intentManager) {
resender.start();
intentManager.registerForIntent(ClientSonyPacketIntent.class, cspi -> handleClientPacket(cspi.getPacket()));
intentManager.registerForIntent(ClientConnectionChangedIntent.class, ccci -> handleClientStatusChanged(ccci.getStatus()));
running.set(true);
executor.start();
}
public void stop() {
resender.stop(true);
resender.awaitTermination(1000);
synchronized (sentPackets) {
sentPackets.clear();
}
running.set(false);
executor.stop(true);
executor.awaitTermination(500);
resender.reset();
}
public void restart() {
synchronized (sentPackets) {
sentPackets.clear();
}
public void add(SequencedOutbound out) {
resender.add(out);
}
public void add(short sequence, byte [] data) {
SequencedOutbound out = new SequencedOutbound(sequence, data);
synchronized (sentPackets) {
sentPackets.add(out);
sentPackets.notifyAll();
private void resender() {
while (running.get()) {
resender.resend();
}
}
private void handleClientPacket(Packet p) {
if (p instanceof Acknowledge)
onAcknowledge(((Acknowledge) p).getSequence());
resender.onAcknowledge(((Acknowledge) p).getSequence());
else if (p instanceof OutOfOrder)
onOutOfOrder(((OutOfOrder) p).getSequence());
resender.onOutOfOrder(((OutOfOrder) p).getSequence());
}
private void handleClientStatusChanged(ClientConnectionStatus status) {
restart();
resender.reset();
}
private void onOutOfOrder(short sequence) {
Log.w("OOO %d", sequence);
synchronized (sentPackets) {
congAvoidance.onOutOfOrder();
updateRtt();
private static class ResenderWrapper {
private final List<SequencedOutbound> sentPackets;
private final CongestionAvoidance congAvoidance;
private final Lock sentPacketsLock;
private final Condition sentPacketsCondition;
private final Resender resender;
public ResenderWrapper(ClientPacketSender sender) {
this.sentPackets = new LinkedList<>();
this.congAvoidance = new CongestionAvoidance();
this.sentPacketsLock = new ReentrantLock(false);
this.sentPacketsCondition = sentPacketsLock.newCondition();
this.resender = new Resender(congAvoidance, sender);
}
}
private void onAcknowledge(short sequence) {
int seqInt = sequence & 0xFFFF;
SequencedOutbound out;
synchronized (sentPackets) {
while (!sentPackets.isEmpty()) {
out = sentPackets.get(0);
if (out.getSequenceInt() <= seqInt || (seqInt < 100 && out.getSequenceInt() > Short.MAX_VALUE-100)) {
sentPackets.remove(0);
} else {
break;
}
public void add(SequencedOutbound seq) {
sentPacketsLock.lock();
try {
sentPackets.add(seq);
sentPacketsCondition.signal();
} finally {
sentPacketsLock.unlock();
}
updateRtt();
congAvoidance.onAcknowledgement();
}
}
private void resendRunnable() {
while (resender.isRunning()) {
synchronized (sentPackets) {
congAvoidance.markBeginningOfWindow();
if (congAvoidance.getWindow() != 50 && congAvoidance.getAverageRTT() != Double.MAX_VALUE) {
Log.d("Congestion Window: %d Average RTT: %.3fms", congAvoidance.getWindow(), congAvoidance.getAverageRTT()/1E6);
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;
}
}
boolean lossEvent = !sentPackets.isEmpty() && (congAvoidance.isTimedOut() || congAvoidance.isTripleACK());
if (lossEvent) {
Log.w("Resender: Loss Event!");
}
sendAllInWindow();
if (sentPackets.size() < 100) {
casualWindowIteration(lossEvent);
} else {
intenseWindowIteration(lossEvent);
}
congAvoidance.markEndOfWindow();
if (waitForPacket())
continue;
congAvoidance.onAcknowledgement();
} finally {
sentPacketsLock.unlock();
}
}
public void resend() {
sentPacketsLock.lock();
try {
waitForPacket();
resender.handle(sentPackets);
} finally {
sentPacketsLock.unlock();
}
waitForTimeoutOrAck();
}
}
private void updateRtt() {
long lastRtt = getTimeSinceSent();
clearTimeSinceSent();
if (lastRtt > 0)
congAvoidance.updateRtt(lastRtt);
}
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 boolean waitForPacket() {
if (!sentPackets.isEmpty())
return false;
try {
while (sentPackets.isEmpty()) {
sentPackets.wait();
private void waitForPacket() {
if (!sentPackets.isEmpty())
return;
try {
while (sentPackets.isEmpty()) {
sentPacketsCondition.await();
}
} catch (InterruptedException e) {
}
} catch (InterruptedException e) {
congAvoidance.clearTimeSinceSent();
}
clearTimeSinceSent();
return true;
}
/**
* 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));
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)));
}
}
/**
* 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 window = congAvoidance.getWindow();
if (window > sentPackets.size())
return;
if (lossEvent) {
congAvoidance.setWindow(Math.max(50, window / 4));
} else {
congAvoidance.setWindow((int) (window * 1.5));
private static class Resender {
private final CongestionAvoidance congAvoidance;
private final ClientPacketSender sender;
public Resender(CongestionAvoidance congAvoidance, ClientPacketSender sender) {
this.congAvoidance = congAvoidance;
this.sender = sender;
}
}
private void sendAllInWindow() {
int max = congAvoidance.getWindow();
for (SequencedOutbound out : sentPackets) {
if (--max < 0)
break;
sender.sendRaw(out.getData());
congAvoidance.onSentPacket();
public void handle(List<SequencedOutbound> sentPackets) {
congAvoidance.markBeginningOfWindow();
int sentPacketCount = sentPackets.size();
boolean lossEvent = sentPacketCount > 0 && (congAvoidance.isTimedOut() || congAvoidance.isTripleACK());
printStatus(lossEvent);
sendAllInWindow(sentPackets);
if (sentPacketCount < 100) {
casualWindowIteration(lossEvent);
} else {
intenseWindowIteration(lossEvent, sentPacketCount);
}
congAvoidance.markEndOfWindow();
}
updateTimeSinceSent();
}
private void updateTimeSinceSent() {
lastSent.compareAndSet(0, System.nanoTime());
}
private long getTimeSinceSent() {
long sent = lastSent.get();
if (sent == 0)
return -1;
return System.nanoTime() - sent;
}
private void clearTimeSinceSent() {
lastSent.set(0);
private void printStatus(boolean lossEvent) {
if (congAvoidance.getWindow() != 50 && congAvoidance.getAverageRTT() != Double.MAX_VALUE)
Log.d("Resender: Congestion Window: %d Average RTT: %.3fms", congAvoidance.getWindow(), congAvoidance.getAverageRTT()/1E6);
if (lossEvent)
Log.w("Resender: Loss Event!");
}
/**
* This congestion avoidance algorithm focuses more on slow window
* increases/decreases. This is ideal for casual gameplay where there
* aren't many packets being sent at once.
*/
private void casualWindowIteration(boolean lossEvent) {
if (lossEvent) {
congAvoidance.setWindow(Math.max(10, congAvoidance.getWindow() - 5));
} else {
congAvoidance.setWindow(Math.min(50, congAvoidance.getWindow() + 5));
}
}
/**
* This congestion avoidance algorithm focuses on fast window
* increases/decreases. This is ideal for zone-in where as many packets as
* possible need to be sent as fast as possible.
*/
private void intenseWindowIteration(boolean lossEvent, int sentPackets) {
int window = congAvoidance.getWindow();
if (window > sentPackets)
return;
if (lossEvent) {
congAvoidance.setWindow(Math.max(50, window / 4));
} else {
congAvoidance.setWindow((int) (window * 1.5));
}
}
private void sendAllInWindow(List<SequencedOutbound> sentPackets) {
int max = congAvoidance.getWindow();
for (SequencedOutbound out : sentPackets) {
if (--max < 0)
break;
sender.sendRaw(out.getData());
congAvoidance.onSentPacket();
}
congAvoidance.updateTimeSinceSent();
}
}
private static class CongestionAvoidance {
// Higher-level properties
private long lastSent;
private double averageRtt;
private int congestionWindow;
@@ -226,25 +262,22 @@ public class ClientPacketResender {
}
public void reset() {
this.lastSent = 0;
this.congestionWindow = 1;
this.outOfOrders = 0;
this.acknowledgements = 0;
this.sentPackets = 0;
this.missedWindows = 0;
this.congestionWindow = 1;
}
public void onOutOfOrder() {
updateRtt();
this.outOfOrders++;
}
public void updateRtt(long lastRtt) {
if (this.averageRtt == Double.MAX_VALUE)
this.averageRtt = lastRtt;
else
this.averageRtt = averageRtt * 0.875 + lastRtt * 0.125;
}
public void onAcknowledgement() {
updateRtt();
this.outOfOrders = 0;
this.acknowledgements++;
}
@@ -285,43 +318,27 @@ public class ClientPacketResender {
return missedWindows >= 2 && averageRtt != Double.MAX_VALUE;
}
}
private static class SequencedOutbound implements SequencedPacket {
private int sequence;
private byte [] data;
public SequencedOutbound(short sequence, byte [] data) {
this.sequence = sequence & 0xFFFF;
this.data = data;
public void updateTimeSinceSent() {
if (lastSent == 0)
lastSent = System.nanoTime();
}
@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;
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;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof SequencedOutbound))
return super.equals(o);
return ((SequencedOutbound) o).getSequence() == sequence;
public void clearTimeSinceSent() {
lastSent = 0;
}
@Override
public int hashCode() {
return sequence;
}
}
}
}

View File

@@ -0,0 +1,40 @@
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;
}
}

View File

@@ -1,181 +0,0 @@
/***********************************************************************************
* Copyright (c) 2015 /// Project SWG /// www.projectswg.com *
* *
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
* Our goal is to create an emulator which will provide a server for players to *
* continue playing a game similar to the one they used to play. We are basing *
* it on the final publish of the game prior to end-game events. *
* *
* This file is part of Holocore. *
* *
* -------------------------------------------------------------------------------- *
* *
* Holocore is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* Holocore is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
package com.projectswg.networking.encryption;
import java.nio.charset.StandardCharsets;
public class CRC {
private static final int CRC_TABLE[] = {
0x0000000,
0x04C11DB7, 0x09823B6E, 0x0D4326D9, 0x130476DC, 0x17C56B6B,
0x1A864DB2, 0x1E475005, 0x2608EDB8, 0x22C9F00F, 0x2F8AD6D6,
0x2B4BCB61, 0x350C9B64, 0x31CD86D3, 0x3C8EA00A, 0x384FBDBD,
0x4C11DB70, 0x48D0C6C7, 0x4593E01E, 0x4152FDA9, 0x5F15ADAC,
0x5BD4B01B, 0x569796C2, 0x52568B75, 0x6A1936C8, 0x6ED82B7F,
0x639B0DA6, 0x675A1011, 0x791D4014, 0x7DDC5DA3, 0x709F7B7A,
0x745E66CD, 0x9823B6E0, 0x9CE2AB57, 0x91A18D8E, 0x95609039,
0x8B27C03C, 0x8FE6DD8B, 0x82A5FB52, 0x8664E6E5, 0xBE2B5B58,
0xBAEA46EF, 0xB7A96036, 0xB3687D81, 0xAD2F2D84, 0xA9EE3033,
0xA4AD16EA, 0xA06C0B5D, 0xD4326D90, 0xD0F37027, 0xDDB056FE,
0xD9714B49, 0xC7361B4C, 0xC3F706FB, 0xCEB42022, 0xCA753D95,
0xF23A8028, 0xF6FB9D9F, 0xFBB8BB46, 0xFF79A6F1, 0xE13EF6F4,
0xE5FFEB43, 0xE8BCCD9A, 0xEC7DD02D, 0x34867077, 0x30476DC0,
0x3D044B19, 0x39C556AE, 0x278206AB, 0x23431B1C, 0x2E003DC5,
0x2AC12072, 0x128E9DCF, 0x164F8078, 0x1B0CA6A1, 0x1FCDBB16,
0x018AEB13, 0x054BF6A4, 0x0808D07D, 0x0CC9CDCA, 0x7897AB07,
0x7C56B6B0, 0x71159069, 0x75D48DDE, 0x6B93DDDB, 0x6F52C06C,
0x6211E6B5, 0x66D0FB02, 0x5E9F46BF, 0x5A5E5B08, 0x571D7DD1,
0x53DC6066, 0x4D9B3063, 0x495A2DD4, 0x44190B0D, 0x40D816BA,
0xACA5C697, 0xA864DB20, 0xA527FDF9, 0xA1E6E04E, 0xBFA1B04B,
0xBB60ADFC, 0xB6238B25, 0xB2E29692, 0x8AAD2B2F, 0x8E6C3698,
0x832F1041, 0x87EE0DF6, 0x99A95DF3, 0x9D684044, 0x902B669D,
0x94EA7B2A, 0xE0B41DE7, 0xE4750050, 0xE9362689, 0xEDF73B3E,
0xF3B06B3B, 0xF771768C, 0xFA325055, 0xFEF34DE2, 0xC6BCF05F,
0xC27DEDE8, 0xCF3ECB31, 0xCBFFD686, 0xD5B88683, 0xD1799B34,
0xDC3ABDED, 0xD8FBA05A, 0x690CE0EE, 0x6DCDFD59, 0x608EDB80,
0x644FC637, 0x7A089632, 0x7EC98B85, 0x738AAD5C, 0x774BB0EB,
0x4F040D56, 0x4BC510E1, 0x46863638, 0x42472B8F, 0x5C007B8A,
0x58C1663D, 0x558240E4, 0x51435D53, 0x251D3B9E, 0x21DC2629,
0x2C9F00F0, 0x285E1D47, 0x36194D42, 0x32D850F5, 0x3F9B762C,
0x3B5A6B9B, 0x0315D626, 0x07D4CB91, 0x0A97ED48, 0x0E56F0FF,
0x1011A0FA, 0x14D0BD4D, 0x19939B94, 0x1D528623, 0xF12F560E,
0xF5EE4BB9, 0xF8AD6D60, 0xFC6C70D7, 0xE22B20D2, 0xE6EA3D65,
0xEBA91BBC, 0xEF68060B, 0xD727BBB6, 0xD3E6A601, 0xDEA580D8,
0xDA649D6F, 0xC423CD6A, 0xC0E2D0DD, 0xCDA1F604, 0xC960EBB3,
0xBD3E8D7E, 0xB9FF90C9, 0xB4BCB610, 0xB07DABA7, 0xAE3AFBA2,
0xAAFBE615, 0xA7B8C0CC, 0xA379DD7B, 0x9B3660C6, 0x9FF77D71,
0x92B45BA8, 0x9675461F, 0x8832161A, 0x8CF30BAD, 0x81B02D74,
0x857130C3, 0x5D8A9099, 0x594B8D2E, 0x5408ABF7, 0x50C9B640,
0x4E8EE645, 0x4A4FFBF2, 0x470CDD2B, 0x43CDC09C, 0x7B827D21,
0x7F436096, 0x7200464F, 0x76C15BF8, 0x68860BFD, 0x6C47164A,
0x61043093, 0x65C52D24, 0x119B4BE9, 0x155A565E, 0x18197087,
0x1CD86D30, 0x029F3D35, 0x065E2082, 0x0B1D065B, 0x0FDC1BEC,
0x3793A651, 0x3352BBE6, 0x3E119D3F, 0x3AD08088, 0x2497D08D,
0x2056CD3A, 0x2D15EBE3, 0x29D4F654, 0xC5A92679, 0xC1683BCE,
0xCC2B1D17, 0xC8EA00A0, 0xD6AD50A5, 0xD26C4D12, 0xDF2F6BCB,
0xDBEE767C, 0xE3A1CBC1, 0xE760D676, 0xEA23F0AF, 0xEEE2ED18,
0xF0A5BD1D, 0xF464A0AA, 0xF9278673, 0xFDE69BC4, 0x89B8FD09,
0x8D79E0BE, 0x803AC667, 0x84FBDBD0, 0x9ABC8BD5, 0x9E7D9662,
0x933EB0BB, 0x97FFAD0C, 0xAFB010B1, 0xAB710D06, 0xA6322BDF,
0xA2F33668, 0xBCB4666D, 0xB8757BDA, 0xB5365D03, 0xB1F740B4,
};
private static final int[] CRC_ZIP_TABLE = {
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
};
public static int getCrc(String input) {
byte [] bytes = input.getBytes(StandardCharsets.UTF_8);
int crc = 0xffffffff;
for (int i = 0; i < bytes.length; ++i) {
crc = CRC_TABLE[(int) (bytes[i] ^ (crc >> 24)) & 0x000000FF] ^ (crc << 8);
}
return (int) ~crc;
}
public static int memcrc(String input) {
byte [] bytes = input.getBytes(StandardCharsets.UTF_8);
int crc = 0xffffffff;
for (int i = 0; i < bytes.length; ++i) {
crc = CRC_TABLE[(int) (bytes[i] ^ (crc >> 24)) & 0x000000FF] ^ (crc << 8);
}
return (int) ~crc;
}
public static int memcrc(byte [] src_buffer, int offset, int length, int seed) {
int index;
int newCRC = 0;
newCRC = CRC_ZIP_TABLE[(~seed) & 0xFF];
newCRC ^= 0x00FFFFFF;
index = (seed >> 8) ^ newCRC;
newCRC = (newCRC >> 8) & 0x00FFFFFF;
newCRC ^= CRC_ZIP_TABLE[index & 0xFF];
index = (seed >> 16) ^ newCRC;
newCRC = (newCRC >> 8) & 0x00FFFFFF;
newCRC ^= CRC_ZIP_TABLE[index & 0xFF];
index = (seed >> 24) ^ newCRC;
newCRC = (newCRC >> 8) & 0x00FFFFFF;
newCRC ^= CRC_ZIP_TABLE[index & 0xFF];
for (int i = offset; i < length; i++) {
index = (src_buffer[i]) ^ newCRC;
newCRC = (newCRC >> 8) & 0x00FFFFFF;
newCRC ^= CRC_ZIP_TABLE[index & 0xFF];
}
return ~newCRC;
}
}

View File

@@ -1,112 +0,0 @@
/***********************************************************************************
* Copyright (c) 2015 /// Project SWG /// www.projectswg.com *
* *
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
* Our goal is to create an emulator which will provide a server for players to *
* continue playing a game similar to the one they used to play. We are basing *
* it on the final publish of the game prior to end-game events. *
* *
* This file is part of Holocore. *
* *
* -------------------------------------------------------------------------------- *
* *
* Holocore is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* Holocore is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
package com.projectswg.networking.resources;
public class Galaxy {
private int id = 0;
private String name = "";
private String address = "";
private short zonePort = (short) 44463;
private short pingPort = (short) 44462;
private int population = 0;
private int popStatus = 0;
private GalaxyStatus status = GalaxyStatus.DOWN;
private int timeZone = 0;
private int maxCharacters = 0;
private int onlinePlayerLimit = 0;
private int onlineFreeTrialLimit = 0;
private boolean recommended = true;
public Galaxy() {
}
public int getId() { return id; }
public String getName() { return name; }
public String getAddress() { return address; }
public short getZonePort() { return zonePort; }
public short getPingPort() { return pingPort; }
public int getPopulation() { return population; }
public int getPopulationStatus() { return popStatus; }
public GalaxyStatus getStatus() { return status; }
public int getTimeZone() { return timeZone; }
public int getMaxCharacters() { return maxCharacters; }
public int getOnlinePlayerLimit() { return onlinePlayerLimit; }
public int getOnlineFreeTrialLimit() { return onlineFreeTrialLimit; }
public boolean isRecommended() { return recommended; }
public void setId(int id) { this.id = id; }
public void setName(String name) { this.name = name; }
public void setAddress(String addr) { this.address = addr; }
public void setZonePort(short port) { this.zonePort = port; }
public void setPingPort(short port) { this.pingPort = port; }
public void setPopulation(int population) { this.population = population; }
public void setPopulationStatus(int status) { this.popStatus = status; }
public void setStatus(GalaxyStatus status) { this.status = status; }
public void setTimeZone(int timeZone) { this.timeZone = timeZone; }
public void setMaxCharacters(int max) { this.maxCharacters = max; }
public void setOnlinePlayerLimit(int max) { this.onlinePlayerLimit = max; }
public void setOnlineFreeTrialLimit(int max) { this.onlineFreeTrialLimit = max; }
public void setRecommended(boolean r) { this.recommended = r; }
public String toString() {
return String.format("Galaxy[ID=%d Name=%s Address=%s Zone=%d Ping=%d Pop=%d PopStat=%d Status=%s Time=%d Max=%d Rec=%b]",
id, name, address, zonePort, pingPort, population, popStatus, status, timeZone, maxCharacters, recommended);
}
public void setStatus(int status) {
for (GalaxyStatus gs : GalaxyStatus.values()) {
if (gs.getStatus() == status) {
setStatus(gs);
return;
}
}
}
public enum GalaxyStatus {
DOWN (0x00),
LOADING (0x01),
UP (0x02),
LOCKED (0x03),
RESTRICTED (0x04),
FULL (0x05);
private byte status;
GalaxyStatus(int status) {
this.status = (byte) status;
}
public byte getStatus() {
return status;
}
}
}

View File

@@ -15,6 +15,7 @@ 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;
@@ -23,8 +24,6 @@ import com.projectswg.intents.ClientToServerPacketIntent;
import com.projectswg.intents.ServerConnectionChangedIntent;
import com.projectswg.intents.ServerToClientPacketIntent;
import com.projectswg.common.network.packets.swg.zone.HeartBeat;
public class ServerConnectionService extends Service {
private static final long HOLOCORE_TIMEOUT = TimeUnit.SECONDS.toNanos(21);