diff --git a/src/network/NetworkClient.java b/src/network/NetworkClient.java index 830ec9ff6..52accc585 100644 --- a/src/network/NetworkClient.java +++ b/src/network/NetworkClient.java @@ -27,6 +27,7 @@ ***********************************************************************************/ package network; +import intents.network.ConnectionClosedIntent; import intents.network.ConnectionOpenedIntent; import intents.network.InboundPacketIntent; @@ -37,19 +38,26 @@ import java.nio.ByteOrder; import java.util.LinkedList; import java.util.List; import java.util.Queue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -import resources.control.Intent; +import resources.network.DisconnectReason; +import resources.network.NetBufferStream; +import utilities.IntentChain; import network.encryption.Compression; import network.packets.Packet; +import network.packets.swg.ErrorMessage; import network.packets.swg.SWGPacket; +import network.packets.swg.holo.HoloPacket; import network.packets.swg.zone.object_controller.ObjectController; public class NetworkClient { private static final int DEFAULT_BUFFER = 128; + private static final int TRY_LOCK_TIME = 100; - private final Object prevPacketIntentMutex = new Object(); + private final IntentChain intentChain = new IntentChain(); private final Object bufferMutex = new Object(); private final ReentrantLock inboundLock = new ReentrantLock(true); private final ReentrantLock outboundLock = new ReentrantLock(true); @@ -57,28 +65,23 @@ public class NetworkClient { private final long networkId; private final PacketSender packetSender; private final Queue outboundQueue; - private Intent prevPacketIntent; - private ByteBuffer buffer; - private long lastBufferSizeModification; + private final NetBufferStream buffer; + private ClientStatus status; public NetworkClient(InetSocketAddress address, long networkId, PacketSender packetSender) { this.address = address; this.networkId = networkId; this.packetSender = packetSender; - this.buffer = ByteBuffer.allocate(DEFAULT_BUFFER); this.outboundQueue = new LinkedList<>(); - lastBufferSizeModification = System.nanoTime(); - prevPacketIntent = null; + this.buffer = new NetBufferStream(DEFAULT_BUFFER); + this.status = ClientStatus.DISCONNECTED; } public void close() { - synchronized (bufferMutex) { - buffer = ByteBuffer.allocate(0); - } - synchronized (prevPacketIntentMutex) { - prevPacketIntent = null; - } + buffer.reset(); + intentChain.reset(); outboundQueue.clear(); + status = ClientStatus.DISCONNECTED; } public InetSocketAddress getAddress() { @@ -90,14 +93,24 @@ public class NetworkClient { } public void onConnected() { - synchronized (prevPacketIntentMutex) { - prevPacketIntent = new ConnectionOpenedIntent(networkId); - prevPacketIntent.broadcast(); - } + status = ClientStatus.CONNECTED; + intentChain.broadcastAfter(new ConnectionOpenedIntent(networkId)); + } + + public void onConnecting() { + status = ClientStatus.CONNECTING; + } + + public void onDisconnected() { + status = ClientStatus.DISCONNECTED; + } + + public ClientStatus getStatus() { + return status; } public void processOutbound() { - if (!outboundLock.tryLock()) + if (!tryLockInterruptable(outboundLock)) return; try { Packet p; @@ -123,42 +136,29 @@ public class NetworkClient { public void addToBuffer(byte [] data) { synchronized (bufferMutex) { - if (data.length > buffer.remaining()) { // Increase size - int nCapacity = buffer.capacity() * 2; - while (nCapacity < buffer.position()+data.length) - nCapacity *= 2; - ByteBuffer bb = ByteBuffer.allocate(nCapacity); - buffer.flip(); - bb.put(buffer); - bb.put(data); - this.buffer = bb; - lastBufferSizeModification = System.nanoTime(); - } else { - buffer.put(data); - if (buffer.position() < buffer.capacity()/4 && (System.nanoTime()-lastBufferSizeModification) >= 1E9) - shrinkBuffer(); - } + buffer.write(data); } } public boolean processInbound() { - if (!inboundLock.tryLock()) + if (!tryLockInterruptable(inboundLock)) return false; try { List packets; synchronized (bufferMutex) { - buffer.flip(); packets = processPackets(); buffer.compact(); } - synchronized (prevPacketIntentMutex) { - for (Packet p : packets) { - p.setAddress(address.getAddress()); - p.setPort(address.getPort()); - InboundPacketIntent i = new InboundPacketIntent(p, networkId); - i.broadcastAfterIntent(prevPacketIntent); - prevPacketIntent = i; + for (Packet p : packets) { + p.setAddress(address.getAddress()); + p.setPort(address.getPort()); + if (status != ClientStatus.CONNECTED && !(p instanceof HoloPacket)) { + addToOutbound(new ErrorMessage("Network Manager", "Upgrade your launcher!", false)); + processOutbound(); + new ConnectionClosedIntent(networkId, DisconnectReason.CONNECTION_REFUSED).broadcast(); + break; } + intentChain.broadcastAfter(new InboundPacketIntent(p, networkId)); } return packets.size() > 0; } finally { @@ -166,21 +166,6 @@ public class NetworkClient { } } - private void shrinkBuffer() { - synchronized (bufferMutex) { - int nCapacity = DEFAULT_BUFFER; - while (nCapacity < buffer.position()) - nCapacity *= 2; - if (nCapacity >= buffer.capacity()) - return; - ByteBuffer bb = ByteBuffer.allocate(nCapacity).order(ByteOrder.LITTLE_ENDIAN); - buffer.flip(); - bb.put(buffer); - buffer = bb; - lastBufferSizeModification = System.nanoTime(); - } - } - private List processPackets() { List packets = new LinkedList<>(); Packet p = null; @@ -199,29 +184,19 @@ public class NetworkClient { private Packet processPacket() throws EOFException { if (buffer.remaining() < 5) throw new EOFException("Not enough remaining data for header! Remaining: " + buffer.remaining()); - buffer.order(ByteOrder.LITTLE_ENDIAN); - byte bitfield = buffer.get(); - boolean compressed = (bitfield & (1<<0)) != 0; - boolean swg = (bitfield & (1<<1)) != 0; + byte bitfield = buffer.getByte(); + boolean compressed = (bitfield & 0x01) != 0; int length = buffer.getShort(); int decompressedLength = buffer.getShort(); if (buffer.remaining() < length) { buffer.position(buffer.position() - 5); throw new EOFException("Not enough remaining data! Remaining: " + buffer.remaining() + " Length: " + length); } - byte [] pData = new byte[length]; - buffer.get(pData); + byte [] pData = buffer.getArray(length); if (compressed) { pData = Compression.decompress(pData, decompressedLength); } - if (swg) - return processSWG(pData); - else - return processProtocol(pData); - } - - private Packet processProtocol(byte [] data) { - return null; + return processSWG(pData); } private SWGPacket processSWG(byte [] data) { @@ -278,8 +253,22 @@ public class NetworkClient { packetSender.sendPacket(address, data); } + private boolean tryLockInterruptable(Lock l) { + try { + return l.tryLock(TRY_LOCK_TIME, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + return false; + } + } + public String toString() { return "NetworkClient["+address+"]"; } + public enum ClientStatus { + DISCONNECTED, + CONNECTING, + CONNECTED + } + } diff --git a/src/network/PacketType.java b/src/network/PacketType.java index b98afb150..27b53cfc3 100644 --- a/src/network/PacketType.java +++ b/src/network/PacketType.java @@ -28,6 +28,9 @@ package network; import network.packets.swg.*; +import network.packets.swg.holo.HoloConnectionStarted; +import network.packets.swg.holo.HoloConnectionStopped; +import network.packets.swg.holo.HoloSetProtocolVersion; import network.packets.swg.login.*; import network.packets.swg.login.creation.*; import network.packets.swg.zone.*; @@ -49,6 +52,11 @@ import resources.server_info.Log; public enum PacketType { + // Holocore + HOLO_SET_PROTOCOL_VERSION (HoloSetProtocolVersion.CRC, HoloSetProtocolVersion.class), + HOLO_CONNECTION_STARTED (HoloConnectionStarted.CRC, HoloConnectionStarted.class), + HOLO_CONNECTION_STOPPED (HoloConnectionStopped.CRC, HoloConnectionStopped.class), + // Both SERVER_UNIX_EPOCH_TIME (ServerUnixEpochTime.CRC, ServerUnixEpochTime.class), SERVER_ID (ServerId.CRC, ServerId.class), diff --git a/src/network/packets/swg/holo/HoloConnectionStarted.java b/src/network/packets/swg/holo/HoloConnectionStarted.java new file mode 100644 index 000000000..4b365680e --- /dev/null +++ b/src/network/packets/swg/holo/HoloConnectionStarted.java @@ -0,0 +1,27 @@ +package network.packets.swg.holo; + +import java.nio.ByteBuffer; + +public class HoloConnectionStarted extends HoloPacket { + + public static final int CRC = resources.common.CRC.getCrc("HoloConnectionStarted"); + + public HoloConnectionStarted() { + + } + + @Override + public void decode(ByteBuffer data) { + if (!super.decode(data, CRC)) + return; + } + + @Override + public ByteBuffer encode() { + ByteBuffer data = ByteBuffer.allocate(6); + addShort(data, 1); + addInt(data, CRC); + return data; + } + +} diff --git a/src/network/packets/swg/holo/HoloConnectionStopped.java b/src/network/packets/swg/holo/HoloConnectionStopped.java new file mode 100644 index 000000000..1bf8460f0 --- /dev/null +++ b/src/network/packets/swg/holo/HoloConnectionStopped.java @@ -0,0 +1,56 @@ +package network.packets.swg.holo; + +import java.nio.ByteBuffer; + +public class HoloConnectionStopped extends HoloPacket { + + public static final int CRC = resources.common.CRC.getCrc("HoloConnectionStopped"); + + private ConnectionStoppedReason reason; + + public HoloConnectionStopped() { + this(ConnectionStoppedReason.UNKNOWN); + } + + public HoloConnectionStopped(ConnectionStoppedReason reason) { + this.reason = reason; + } + + @Override + public void decode(ByteBuffer data) { + if (!super.decode(data, CRC)) + return; + try { + reason = ConnectionStoppedReason.valueOf(getAscii(data)); + } catch (IllegalArgumentException e) { + reason = ConnectionStoppedReason.UNKNOWN; + } + } + + @Override + public ByteBuffer encode() { + ByteBuffer data = ByteBuffer.allocate(8+reason.name().length()); + addShort(data, 1); + addInt(data, CRC); + addAscii(data, reason.name()); + return data; + } + + public void setReason(ConnectionStoppedReason reason) { + this.reason = reason; + } + + public ConnectionStoppedReason getReason() { + return reason; + } + + public static enum ConnectionStoppedReason { + APPLICATION, + INVALID_PROTOCOL, + OTHER_SIDE_TERMINATED, + NETWORK, + SERVER_ERROR, + UNKNOWN + } + +} diff --git a/src/network/packets/swg/holo/HoloPacket.java b/src/network/packets/swg/holo/HoloPacket.java new file mode 100644 index 000000000..f1cdb7529 --- /dev/null +++ b/src/network/packets/swg/holo/HoloPacket.java @@ -0,0 +1,12 @@ +package network.packets.swg.holo; + +import java.nio.ByteBuffer; + +import network.packets.swg.SWGPacket; + +public abstract class HoloPacket extends SWGPacket { + + public abstract void decode(ByteBuffer data); + public abstract ByteBuffer encode(); + +} diff --git a/src/network/packets/swg/holo/HoloSetProtocolVersion.java b/src/network/packets/swg/holo/HoloSetProtocolVersion.java new file mode 100644 index 000000000..98bf0bfa5 --- /dev/null +++ b/src/network/packets/swg/holo/HoloSetProtocolVersion.java @@ -0,0 +1,43 @@ +package network.packets.swg.holo; + +import java.nio.ByteBuffer; + +public class HoloSetProtocolVersion extends HoloPacket { + + public static final int CRC = resources.common.CRC.getCrc("HoloSetProtocolVersion"); + + private String protocol; + + public HoloSetProtocolVersion() { + this(""); + } + + public HoloSetProtocolVersion(String protocol) { + this.protocol = protocol; + } + + @Override + public void decode(ByteBuffer data) { + if (!super.decode(data, CRC)) + return; + protocol = getAscii(data); + } + + @Override + public ByteBuffer encode() { + ByteBuffer data = ByteBuffer.allocate(8 + protocol.length()); + addShort(data, 2); + addInt(data, CRC); + addAscii(data, protocol); + return data; + } + + public String getProtocol() { + return protocol; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + +} diff --git a/src/resources/network/NetBuffer.java b/src/resources/network/NetBuffer.java index 2b1e61de0..024105d40 100644 --- a/src/resources/network/NetBuffer.java +++ b/src/resources/network/NetBuffer.java @@ -200,6 +200,12 @@ public class NetBuffer { return bData; } + public byte [] getArray(int size) { + byte [] bData = new byte[size]; + data.get(bData); + return bData; + } + public Object getGeneric(Class type) { if (Encodable.class.isAssignableFrom(type)) { T instance = null; diff --git a/src/resources/network/NetBufferStream.java b/src/resources/network/NetBufferStream.java new file mode 100644 index 000000000..4e3f3ab4a --- /dev/null +++ b/src/resources/network/NetBufferStream.java @@ -0,0 +1,264 @@ +package resources.network; + +import java.io.OutputStream; +import java.nio.ByteBuffer; + +import resources.collections.SWGList; +import resources.collections.SWGMap; +import resources.collections.SWGSet; +import resources.encodables.Encodable; +import utilities.Encoder.StringType; + +public class NetBufferStream extends OutputStream { + + private final Object expansionMutex; + private final Object bufferMutex; + private NetBuffer buffer; + private int capacity; + private int size; + private int mark; + + public NetBufferStream() { + this(1024); + } + + public NetBufferStream(int size) { + if (size <= 0) + throw new NegativeArraySizeException("Size cannot be less than or equal to 0!"); + this.expansionMutex = new Object(); + this.bufferMutex = new Object(); + this.buffer = NetBuffer.allocate(size); + this.capacity = size; + this.size = 0; + this.mark = 0; + } + + @Override + public void close() { + reset(); + } + + @Override + public void flush() { + + } + + /** + * Sets the mark to the buffer's current position + */ + public void mark() { + mark = buffer.position(); + } + + /** + * Rewinds the buffer to the previously set mark + */ + public void rewind() { + buffer.position(mark); + mark = 0; + } + + /** + * Resets the buffer to the default capacity and clears all data + */ + public void reset() { + synchronized (expansionMutex) { + synchronized (bufferMutex) { + buffer = NetBuffer.allocate(1024); + capacity = 1024; + size = 0; + mark = 0; + } + } + } + + @Override + public void write(int b) { + ensureCapacity(size + 1); + synchronized (bufferMutex) { + buffer.array()[size] = (byte) b; + size++; + } + } + + public void write(byte [] data) { + write(data, 0, data.length); + } + + public void write(byte [] data, int offset, int length) { + ensureCapacity(size + length); + synchronized (bufferMutex) { + System.arraycopy(data, offset, buffer.array(), size, length); + size += length; + } + } + + public void write(ByteBuffer data) { + ensureCapacity(size + data.remaining()); + synchronized (bufferMutex) { + while (data.hasRemaining()) { + buffer.array()[size++] = data.get(); + } + } + } + + /** + * Moves all data from the buffer's current position to position 0. This + * method also adjusts the mark to be pointing to the same data + */ + public void compact() { + synchronized (bufferMutex) { + byte [] data = buffer.array(); + for (int i = buffer.position(), j = 0; i < size; ++i, ++j) { + data[j] = data[i]; + } + size -= buffer.position(); + mark -= buffer.position(); + buffer.position(0); + } + } + + public int remaining() { + return size - buffer.position(); + } + + public boolean hasRemaining() { + return remaining() > 0; + } + + public int position() { + return buffer.position(); + } + + public void position(int position) { + buffer.position(position); + } + + public void seek(int relative) { + buffer.seek(relative); + } + + public ByteBuffer getBuffer() { + return buffer.getBuffer(); + } + + public boolean getBoolean() { + return buffer.getBoolean(); + } + + public String getAscii() { + return buffer.getAscii(); + } + + public String getUnicode() { + return buffer.getUnicode(); + } + + public String getString(StringType type) { + return buffer.getString(type); + } + + public byte getByte() { + return buffer.getByte(); + } + + public short getShort() { + return buffer.getShort(); + } + + public int getInt() { + return buffer.getInt(); + } + + public float getFloat() { + return buffer.getFloat(); + } + + public long getLong() { + return buffer.getLong(); + } + + public short getNetShort() { + return buffer.getNetShort(); + } + + public int getNetInt() { + return buffer.getNetInt(); + } + + public long getNetLong() { + return buffer.getNetLong(); + } + + public byte[] getArray() { + return buffer.getArray(); + } + + public byte[] getArray(int size) { + return buffer.getArray(size); + } + + public Object getGeneric(Class type) { + return buffer.getGeneric(type); + } + + public T getEncodable(Class type) { + return buffer.getEncodable(type); + } + + public SWGSet getSwgSet(int num, int var, StringType type) { + return buffer.getSwgSet(num, var, type); + } + + public SWGSet getSwgSet(int num, int var, Class type) { + return buffer.getSwgSet(num, var, type); + } + + public SWGList getSwgList(int num, int var, StringType type) { + return buffer.getSwgList(num, var, type); + } + + public SWGList getSwgList(int num, int var, Class type) { + return buffer.getSwgList(num, var, type); + } + + public SWGMap getSwgMap(int num, int var, StringType key, StringType val) { + return buffer.getSwgMap(num, var, key, val); + } + + public SWGMap getSwgMap(int num, int var, StringType key, Class val) { + return buffer.getSwgMap(num, var, key, val); + } + + public SWGMap getSwgMap(int num, int var, Class key, Class val) { + return buffer.getSwgMap(num, var, key, val); + } + + public byte [] array() { + return buffer.array(); + } + + public int size() { + return size; + } + + public int capacity() { + return capacity; + } + + private void ensureCapacity(int size) { + if (size <= capacity) + return; + synchronized (expansionMutex) { + while (size > capacity) + capacity <<= 2; + synchronized (bufferMutex) { + NetBuffer buf = NetBuffer.allocate(capacity); + System.arraycopy(buffer.array(), 0, buf.array(), 0, this.size); + buf.position(buffer.position()); + this.buffer = buf; + } + } + } + +} diff --git a/src/services/galaxy/ConnectionService.java b/src/services/galaxy/ConnectionService.java index 64bc7ae47..0424771b7 100644 --- a/src/services/galaxy/ConnectionService.java +++ b/src/services/galaxy/ConnectionService.java @@ -131,7 +131,7 @@ public class ConnectionService extends Service { } break; } - case PE_ZONE_IN_CLIENT: + case PE_ZONE_IN_SERVER: clearPlayerFlag(pei.getPlayer(), pei.getEvent(), PlayerFlags.LD); break; case PE_LOGGED_OUT: diff --git a/src/services/galaxy/EnvironmentService.java b/src/services/galaxy/EnvironmentService.java index d7e6995a1..d9b9f350b 100644 --- a/src/services/galaxy/EnvironmentService.java +++ b/src/services/galaxy/EnvironmentService.java @@ -74,7 +74,7 @@ public final class EnvironmentService extends Service { weatherForTerrain.put(t, randomWeather()); executor.scheduleAtFixedRate(new WeatherChanger(t), 0, cycleDuration, TimeUnit.SECONDS); } - executor.scheduleAtFixedRate(() -> { updateTime(); }, 0, 1, TimeUnit.SECONDS); + executor.scheduleAtFixedRate(() -> { updateTime(); }, 0, 30, TimeUnit.SECONDS); return super.initialize(); } diff --git a/src/services/galaxy/GalacticManager.java b/src/services/galaxy/GalacticManager.java index 02c277fc1..2db4caaa6 100644 --- a/src/services/galaxy/GalacticManager.java +++ b/src/services/galaxy/GalacticManager.java @@ -94,6 +94,10 @@ public class GalacticManager extends Manager { synchronized (prevIntentMap) { prevIntentMap.remove(((ConnectionClosedIntent) i).getNetworkId()); } + } else if (i instanceof ConnectionOpenedIntent) { + synchronized (prevIntentMap) { + prevIntentMap.put(((ConnectionOpenedIntent) i).getNetworkId(), i); + } } } diff --git a/src/services/network/NetworkClientManager.java b/src/services/network/NetworkClientManager.java index 771889837..7b141515e 100644 --- a/src/services/network/NetworkClientManager.java +++ b/src/services/network/NetworkClientManager.java @@ -29,6 +29,7 @@ package services.network; import intents.network.CloseConnectionIntent; import intents.network.ConnectionClosedIntent; +import intents.network.InboundPacketIntent; import intents.network.OutboundPacketIntent; import java.io.IOException; @@ -47,8 +48,15 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import network.NetworkClient; +import network.NetworkClient.ClientStatus; import network.PacketSender; import network.packets.Packet; +import network.packets.swg.ErrorMessage; +import network.packets.swg.holo.HoloConnectionStarted; +import network.packets.swg.holo.HoloConnectionStopped; +import network.packets.swg.holo.HoloPacket; +import network.packets.swg.holo.HoloSetProtocolVersion; +import network.packets.swg.holo.HoloConnectionStopped.ConnectionStoppedReason; import resources.config.ConfigFile; import resources.control.Intent; import resources.control.Manager; @@ -60,6 +68,8 @@ import utilities.ThreadUtilities; public class NetworkClientManager extends Manager implements TCPCallback, PacketSender { + private static final String PROTOCOL = "2016-04-13"; + private final Map sockets; private final Map clients; private final Queue inboundQueue; @@ -84,6 +94,7 @@ public class NetworkClientManager extends Manager implements TCPCallback, Packet processOutboundRunnable = () -> processOutboundRunnable(); tcpServer = new TCPServer(getBindPort(), getBufferSize()); + registerForIntent(InboundPacketIntent.TYPE); registerForIntent(OutboundPacketIntent.TYPE); registerForIntent(CloseConnectionIntent.TYPE); } @@ -122,16 +133,81 @@ public class NetworkClientManager extends Manager implements TCPCallback, Packet @Override public void onIntentReceived(Intent i) { - if (i instanceof OutboundPacketIntent) { - Packet p = ((OutboundPacketIntent)i).getPacket(); - if (p != null) - handleOutboundPacket(((OutboundPacketIntent) i).getNetworkId(), p); - } else if (i instanceof CloseConnectionIntent) { - long netId = ((CloseConnectionIntent)i).getNetworkId(); - deleteSession(netId); + switch (i.getType()) { + case InboundPacketIntent.TYPE: + if (i instanceof InboundPacketIntent) + processInboundPacketIntent((InboundPacketIntent) i); + break; + case OutboundPacketIntent.TYPE: + if (i instanceof OutboundPacketIntent) + processOutboundPacketIntent((OutboundPacketIntent) i); + break; + case CloseConnectionIntent.TYPE: + if (i instanceof CloseConnectionIntent) + processCloseConnectionIntent((CloseConnectionIntent) i); + break; } } + private void processInboundPacketIntent(InboundPacketIntent i) { + if (!(i.getPacket() instanceof HoloPacket)) { + NetworkClient client = getClient(i.getNetworkId()); + if (client != null && client.getStatus() != ClientStatus.CONNECTED) { + client.addToOutbound(new ErrorMessage("Network Manager", "Upgrade your launcher!", false)); + client.processOutbound(); + deleteSession(i.getNetworkId(), ConnectionStoppedReason.INVALID_PROTOCOL); + } + return; + } + HoloPacket packet = (HoloPacket) i.getPacket(); + if (packet instanceof HoloSetProtocolVersion) + processSetProtocolVersion((HoloSetProtocolVersion) packet, i.getNetworkId()); + } + + private void processOutboundPacketIntent(OutboundPacketIntent i) { + Packet p = i.getPacket(); + if (p != null) + handleOutboundPacket(i.getNetworkId(), p); + } + + private void processCloseConnectionIntent(CloseConnectionIntent i) { + deleteSession(i.getNetworkId(), getHolocoreReason(i.getDisconnectReason())); + } + + private void processSetProtocolVersion(HoloSetProtocolVersion packet, long networkId) { + NetworkClient client = getClient(networkId); + if (client == null) { + Log.w(this, "NetworkClient not found for ID: %d!", networkId); + deleteSession(networkId, ConnectionStoppedReason.SERVER_ERROR); + return; + } + if (!packet.getProtocol().equals(PROTOCOL)) { + Log.w(this, "Incoming connection has incorrect protocol version! Expected: %s Actual: %s", PROTOCOL, packet.getProtocol()); + deleteSession(networkId, ConnectionStoppedReason.INVALID_PROTOCOL); + return; + } + client.onConnected(); + Intent i = new OutboundPacketIntent(new HoloSetProtocolVersion(PROTOCOL), networkId); + i.broadcast(); + new OutboundPacketIntent(new HoloConnectionStarted(), networkId).broadcastAfterIntent(i); + } + + private ConnectionStoppedReason getHolocoreReason(DisconnectReason reason) { + switch (reason) { + case APPLICATION: + return ConnectionStoppedReason.APPLICATION; + case CONNECTION_REFUSED: + return ConnectionStoppedReason.NETWORK; + case NEW_CONNECTION_ATTEMPT: + return ConnectionStoppedReason.NETWORK; + case OTHER_SIDE_TERMINATED: + return ConnectionStoppedReason.OTHER_SIDE_TERMINATED; + case TIMEOUT: + return ConnectionStoppedReason.NETWORK; + } + return ConnectionStoppedReason.APPLICATION; + } + @Override public void onIncomingConnection(Socket s) { SocketAddress addr = s.getRemoteSocketAddress(); @@ -178,7 +254,6 @@ public class NetworkClientManager extends Manager implements TCPCallback, Packet sockets.put(address, networkId); clients.put(networkId, client); } - client.onConnected(); } private void onSessionDisconnect(InetSocketAddress address) { @@ -187,20 +262,23 @@ public class NetworkClientManager extends Manager implements TCPCallback, Packet networkId = sockets.get(address); } if (networkId != null) { - deleteSession(networkId); + deleteSession(networkId, ConnectionStoppedReason.OTHER_SIDE_TERMINATED); new ConnectionClosedIntent(networkId, DisconnectReason.OTHER_SIDE_TERMINATED).broadcast(); } else { - System.err.println("Network ID not found for " + address + "!"); + Log.w(this, "Network ID not found for " + address + "!"); } } - private void deleteSession(long networkId) { + private void deleteSession(long networkId, ConnectionStoppedReason reason) { synchronized (clients) { NetworkClient client = clients.remove(networkId); if (client == null) { - System.err.println("No NetworkClient found for network id: " + networkId); + Log.w(this, "No NetworkClient found for network id: " + networkId); return; } + client.addToOutbound(new HoloConnectionStopped(reason)); + client.processOutbound(); + tcpServer.disconnect(client.getAddress()); sockets.remove(client.getAddress()); synchronized (inboundQueue) { inboundQueue.remove(client); @@ -208,15 +286,13 @@ public class NetworkClientManager extends Manager implements TCPCallback, Packet synchronized (outboundQueue) { outboundQueue.remove(client); } + client.onDisconnected(); client.close(); } } private void handleOutboundPacket(long networkId, Packet p) { - NetworkClient client; - synchronized (clients) { - client = clients.get(networkId); - } + NetworkClient client = getClient(networkId); if (client != null) { client.addToOutbound(p); synchronized (outboundQueue) { @@ -233,10 +309,7 @@ public class NetworkClientManager extends Manager implements TCPCallback, Packet Log.w(this, "Unknown socket address! Address: %s", addr); return; } - NetworkClient client; - synchronized (clients) { - client = clients.get(netId); - } + NetworkClient client = getClient(netId); if (client != null) { client.addToBuffer(data); synchronized (inboundQueue) { @@ -247,6 +320,12 @@ public class NetworkClientManager extends Manager implements TCPCallback, Packet Log.w(this, "Unknown connection! Network ID: %d Address: %s", netId, addr); } + private NetworkClient getClient(long networkId) { + synchronized (clients) { + return clients.get(networkId); + } + } + private void processBufferRunnable() { try { NetworkClient client; diff --git a/src/services/player/PlayerManager.java b/src/services/player/PlayerManager.java index 044c5b66e..7e1f58119 100644 --- a/src/services/player/PlayerManager.java +++ b/src/services/player/PlayerManager.java @@ -274,8 +274,6 @@ public class PlayerManager extends Manager { if (p != null) { p.setPlayerState(PlayerState.DISCONNECTED); new PlayerEventIntent(p, PlayerEvent.PE_LOGGED_OUT).broadcast(); - } else { - System.err.println("No player found for ID: " + cci.getNetworkId()); } } } diff --git a/src/utilities/IntentChain.java b/src/utilities/IntentChain.java new file mode 100644 index 000000000..a1e6b55d6 --- /dev/null +++ b/src/utilities/IntentChain.java @@ -0,0 +1,28 @@ +package utilities; + +import resources.control.Intent; + +public class IntentChain { + + private final Object mutex; + private Intent i; + + public IntentChain() { + mutex = new Object(); + i = null; + } + + public void reset() { + synchronized (mutex) { + i = null; + } + } + + public void broadcastAfter(Intent i) { + synchronized (mutex) { + i.broadcastAfterIntent(this.i); + this.i = i; + } + } + +} diff --git a/test/resources/network/TestNetBufferStream.java b/test/resources/network/TestNetBufferStream.java new file mode 100644 index 000000000..cb2a0e17b --- /dev/null +++ b/test/resources/network/TestNetBufferStream.java @@ -0,0 +1,119 @@ +package resources.network; + +import java.nio.charset.StandardCharsets; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class TestNetBufferStream { + + @Test + public void testExpansionSingle() { + try (NetBufferStream stream = new NetBufferStream(4)) { + for (int i = 0; i < 10; i++) + stream.write(5); + } + } + + @Test + public void testExpansionBulk() { + byte [] data = new byte[1024]; + try (NetBufferStream stream = new NetBufferStream(4)) { + for (int i = 0; i < 10; i++) + stream.write(data); + } + } + + @Test + public void testWriteReadString() { + try (NetBufferStream stream = new NetBufferStream(4)) { + for (int i = 0; i < 10; i++) + stream.write(generateTestString(i)); + for (int i = 0; i < 10; i++) + Assert.assertEquals(getTestString(i), stream.getAscii()); + } + } + + @Test + public void testWriteReadCompactString() { + try (NetBufferStream stream = new NetBufferStream(4)) { + for (int i = 0; i < 10; i++) + stream.write(generateTestString(i)); + for (int i = 0; i < 10; i++) { + Assert.assertEquals(getTestString(i), stream.getAscii()); + if (i == 4) { + int rem = stream.remaining(); + stream.compact(); + Assert.assertEquals(0, stream.position()); + Assert.assertEquals(rem, stream.remaining()); + } + } + } + } + + @Test + public void testWriteReadInterleave() { + try (NetBufferStream stream = new NetBufferStream(4)) { + for (int i = 0; i < 10; i++) { + stream.write(generateTestString(i)); + if (i % 2 == 1) { + Assert.assertEquals(getTestString(i-1), stream.getAscii()); + Assert.assertEquals(getTestString(i), stream.getAscii()); + } + } + Assert.assertEquals(0, stream.remaining()); + } + } + + @Test + public void testWriteReadCompactInterleave() { + try (NetBufferStream stream = new NetBufferStream(4)) { + for (int i = 0; i < 10; i++) { + stream.write(generateTestString(i)); + if (i % 2 == 1) { + Assert.assertEquals(getTestString(i-1), stream.getAscii()); + Assert.assertEquals(getTestString(i), stream.getAscii()); + int rem = stream.remaining(); + stream.compact(); + Assert.assertEquals(0, stream.position()); + Assert.assertEquals(rem, stream.remaining()); + } + } + Assert.assertEquals(0, stream.remaining()); + } + } + + @Test + public void testRewind() { + try (NetBufferStream stream = new NetBufferStream(4)) { + for (int i = 0; i < 10; i++) + stream.write(generateTestString(i)); + for (int i = 0; i < 10; i++) { + Assert.assertEquals(getTestString(i), stream.getAscii()); + if (i == 4) + stream.mark(); + } + Assert.assertEquals(0, stream.remaining()); + stream.rewind(); + Assert.assertEquals(5*(2+getTestString(0).length()), stream.remaining()); + for (int i = 5; i < 10; i++) + Assert.assertEquals(getTestString(i), stream.getAscii()); + } + } + + private byte [] generateTestString(int num) { + String test = getTestString(num); + byte [] data = new byte[2 + test.length()]; + data[0] = (byte) test.length(); + System.arraycopy(test.getBytes(StandardCharsets.US_ASCII), 0, data, 2, data[0]); + return data; + } + + private String getTestString(int num) { + return "Hello World " + num + "!"; + } + +}