Converted client-holocore from java to kotlin

This commit is contained in:
Obique PSWG
2019-10-08 19:26:59 -04:00
parent c544a28eb7
commit 1fb38ecf37
14 changed files with 518 additions and 513 deletions

View File

@@ -1,17 +1,21 @@
plugins {
id 'java'
id "java"
id "org.javamodularity.moduleplugin"
id "org.jetbrains.kotlin.jvm"
}
sourceCompatibility = 12
repositories {
mavenLocal()
jcenter()
}
dependencies {
compile project(':pswgcommon')
compile group: 'me.joshlarson', name: 'jlcommon-network', version: '1.0.0'
compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib', version: '1.3.50'
compile group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: '1.3.0'
testCompile 'junit:junit:4.12'
}

View File

@@ -0,0 +1,37 @@
package com.projectswg.holocore.client
import java.io.Closeable
import java.io.EOFException
import java.io.InputStream
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
class HolocoreInputStream(private val stream: InputStream): Closeable {
private val lock = ReentrantLock(false)
private val protocol = SWGProtocol()
private val buffer = ByteArray(32*1024)
fun read(): RawPacket {
lock.withLock {
if (protocol.hasPacket()) {
val packet = protocol.disassemble()
if (packet != null)
return packet
}
do {
val n = stream.read(buffer)
if (n <= 0)
throw EOFException("end of stream")
if (protocol.addToBuffer(buffer, 0, n))
return protocol.disassemble() ?: continue
} while (true)
}
throw AssertionError("unreachable code")
}
override fun close() {
stream.close()
}
}

View File

@@ -0,0 +1,23 @@
package com.projectswg.holocore.client
import java.io.Closeable
import java.io.OutputStream
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
class HolocoreOutputStream(private val stream: OutputStream): Closeable {
private val lock = ReentrantLock(false)
private val protocol = SWGProtocol()
fun write(buffer: ByteArray) {
lock.withLock {
stream.write(protocol.assemble(buffer).array())
}
}
override fun close() {
stream.close()
}
}

View File

@@ -1,69 +0,0 @@
package com.projectswg.holocore.client;
import com.projectswg.common.network.NetBuffer;
import com.projectswg.common.network.NetBufferStream;
import java.nio.ByteBuffer;
class HolocoreProtocol {
public static final String VERSION = "2018-02-04";
private static final byte [] EMPTY_PACKET = new byte[0];
private final NetBufferStream inboundStream;
public HolocoreProtocol() {
this.inboundStream = new NetBufferStream();
}
public void reset() {
inboundStream.reset();
}
public NetBuffer assemble(byte [] raw) {
NetBuffer data = NetBuffer.allocate(raw.length + 4); // large array
data.addArrayLarge(raw);
data.flip();
return data;
}
public boolean addToBuffer(ByteBuffer data) {
synchronized (inboundStream) {
inboundStream.write(data);
return hasPacket();
}
}
public byte [] disassemble() {
synchronized (inboundStream) {
if (inboundStream.remaining() < 4) {
return EMPTY_PACKET;
}
inboundStream.mark();
int messageLength = inboundStream.getInt();
if (inboundStream.remaining() < messageLength) {
inboundStream.rewind();
return EMPTY_PACKET;
}
byte [] data = inboundStream.getArray(messageLength);
inboundStream.compact();
return data;
}
}
public boolean hasPacket() {
synchronized (inboundStream) {
if (inboundStream.remaining() < 4)
return false;
inboundStream.mark();
try {
int messageLength = inboundStream.getInt();
return inboundStream.remaining() >= messageLength;
} finally {
inboundStream.rewind();
}
}
}
}

View File

@@ -0,0 +1,82 @@
package com.projectswg.holocore.client
import com.projectswg.common.network.NetBuffer
import com.projectswg.common.network.NetBufferStream
import java.nio.ByteBuffer
internal class HolocoreProtocol {
private val inboundStream: NetBufferStream = NetBufferStream()
fun reset() {
inboundStream.reset()
}
fun assemble(raw: ByteArray): NetBuffer {
val data = NetBuffer.allocate(raw.size + 4) // large array
data.addArrayLarge(raw)
data.flip()
return data
}
fun addToBuffer(data: ByteBuffer): Boolean {
synchronized(inboundStream) {
inboundStream.write(data)
return hasPacket()
}
}
fun addToBuffer(data: ByteArray): Boolean {
synchronized(inboundStream) {
inboundStream.write(data)
return hasPacket()
}
}
fun addToBuffer(data: ByteArray, offset: Int, length: Int): Boolean {
synchronized(inboundStream) {
inboundStream.write(data, offset, length)
return hasPacket()
}
}
fun disassemble(): ByteArray {
synchronized(inboundStream) {
if (inboundStream.remaining() < 4) {
return EMPTY_PACKET
}
inboundStream.mark()
val messageLength = inboundStream.int
if (inboundStream.remaining() < messageLength) {
inboundStream.rewind()
return EMPTY_PACKET
}
val data = inboundStream.getArray(messageLength)
inboundStream.compact()
return data
}
}
fun hasPacket(): Boolean {
synchronized(inboundStream) {
if (inboundStream.remaining() < 4)
return false
inboundStream.mark()
try {
val messageLength = inboundStream.int
return inboundStream.remaining() >= messageLength
} finally {
inboundStream.rewind()
}
}
}
companion object {
const val VERSION = "2018-02-04"
private val EMPTY_PACKET = ByteArray(0)
}
}

View File

@@ -1,371 +0,0 @@
package com.projectswg.holocore.client;
import com.projectswg.common.network.NetBuffer;
import com.projectswg.common.network.packets.swg.holo.HoloConnectionStarted;
import com.projectswg.common.network.packets.swg.holo.HoloConnectionStopped;
import com.projectswg.common.network.packets.swg.holo.HoloConnectionStopped.ConnectionStoppedReason;
import com.projectswg.common.network.packets.swg.holo.HoloSetProtocolVersion;
import me.joshlarson.jlcommon.concurrency.Delay;
import me.joshlarson.jlcommon.log.Log;
import me.joshlarson.jlcommon.network.SSLEngineWrapper.SSLClosedException;
import me.joshlarson.jlcommon.network.SecureTCPSocket;
import me.joshlarson.jlcommon.network.TCPSocket;
import me.joshlarson.jlcommon.network.TCPSocket.TCPSocketCallback;
import me.joshlarson.jlcommon.network.UDPServer;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Locale;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
public class HolocoreSocket implements AutoCloseable {
private final SWGProtocol swgProtocol;
private final AtomicReference<ServerConnectionStatus> status;
private final UDPServer udpServer;
private final BlockingQueue<DatagramPacket> udpInboundQueue;
private final BlockingQueue<RawPacket> inboundQueue;
private final boolean verifyServer;
private SecureTCPSocket socket;
private StatusChangedCallback callback;
private InetSocketAddress address;
public HolocoreSocket(InetAddress addr, int port) {
this(addr, port, true);
}
public HolocoreSocket(InetAddress addr, int port, boolean verifyServer) {
this.swgProtocol = new SWGProtocol();
this.status = new AtomicReference<>(ServerConnectionStatus.DISCONNECTED);
this.udpInboundQueue = new LinkedBlockingQueue<>();
this.inboundQueue = new LinkedBlockingQueue<>();
this.verifyServer = verifyServer;
this.udpServer = createUDPServer();
this.socket = null;
this.callback = null;
this.address = new InetSocketAddress(addr, port);
}
/**
* Shuts down any miscellaneous resources--such as the query UDP server
*/
public void close() {
if (udpServer != null)
udpServer.close();
udpInboundQueue.clear();
disconnect(ConnectionStoppedReason.APPLICATION);
TCPSocket socket = this.socket;
if (socket != null)
socket.disconnect();
}
/**
* Shuts down any miscellaneous resources--such as the query UDP server
*/
@Deprecated
public void terminate() {
close();
}
/**
* Sets a callback for when the status of the server socket changes
* @param callback the callback
*/
public void setStatusChangedCallback(StatusChangedCallback callback) {
this.callback = callback;
}
/**
* Sets the remote address this socket will attempt to connect to
* @param addr the destination address
* @param port the destination port
*/
public void setRemoteAddress(InetAddress addr, int port) {
this.address = new InetSocketAddress(addr, port);
}
/**
* Returns the remote address this socket is pointing to
* @return the remote address as an InetSocketAddress
*/
public InetSocketAddress getRemoteAddress() {
return address;
}
/**
* Gets the current connection state of the socket
* @return the connection state
*/
public ServerConnectionStatus getConnectionState() {
return status.get();
}
/**
* Returns whether or not this socket is disconnected
* @return TRUE if disconnected, FALSE otherwise
*/
public boolean isDisconnected() {
return status.get() == ServerConnectionStatus.DISCONNECTED;
}
/**
* Returns whether or not this socket is connecting
* @return TRUE if connecting, FALSE otherwise
*/
public boolean isConnecting() {
return status.get() == ServerConnectionStatus.CONNECTING;
}
/**
* Returns whether or not this socket is connected
* @return TRUE if connected, FALSE otherwise
*/
public boolean isConnected() {
return status.get() == ServerConnectionStatus.CONNECTED;
}
/**
* Retrieves the server status via a UDP query, with the default timeout of 2000ms
* @return the server status as a string
*/
public String getServerStatus() {
return getServerStatus(2000);
}
/**
* Retrives the server status via a UDP query, with the specified timeout
* @param timeout the timeout in milliseconds
* @return the server status as a string
*/
public String getServerStatus(long timeout) {
Log.t("Requesting server status from %s", address);
if (!udpServer.isRunning()) {
try {
udpServer.bind();
} catch (SocketException e) {
return "UNKNOWN";
}
}
udpServer.send(address, new byte[]{1});
try {
DatagramPacket packet = udpInboundQueue.poll(timeout, TimeUnit.MILLISECONDS);
if (packet == null)
return "OFFLINE";
NetBuffer data = NetBuffer.wrap(packet.getData());
data.getByte();
return data.getAscii();
} catch (InterruptedException e) {
Log.w("Interrupted while waiting for server status response");
return "UNKNOWN";
}
}
/**
* Attempts to connect to the remote server. This call is a blocking function that will not
* return until it has either successfully connected or has failed. It starts by initializing a
* TCP connection, then initializes the Holocore connection, then returns.
* @param timeout the timeout for the connect call
* @return TRUE if successful and connected, FALSE on error
*/
public boolean connect(int timeout) {
try {
SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
TrustManager [] tm = verifyServer ? null : new TrustManager[]{new TrustingTrustManager()};
sslContext.init(null, tm, new SecureRandom());
socket = new SecureTCPSocket(address, sslContext, Runnable::run);
} catch (NoSuchAlgorithmException | KeyManagementException e) {
throw new RuntimeException(e);
}
inboundQueue.clear();
return finishConnection(socket, timeout);
}
private boolean finishConnection(SecureTCPSocket socket, int timeout) {
updateStatus(ServerConnectionStatus.CONNECTING, ServerConnectionChangedReason.NONE);
try {
socket.createConnection();
socket.setCallback(new TCPSocketCallback() {
@Override
public void onIncomingData(TCPSocket socket, ByteBuffer data) {
swgProtocol.addToBuffer(data);
while (true) {
RawPacket packet = swgProtocol.disassemble();
if (packet != null) {
handlePacket(packet.getCrc(), packet.getData());
inboundQueue.offer(packet);
} else
break;
}
}
@Override
public void onError(TCPSocket socket, Throwable t) {
if (t instanceof ClosedChannelException || t instanceof SSLClosedException)
return;
Log.e(t);
}
@Override
public void onDisconnected(TCPSocket socket) { updateStatus(ServerConnectionStatus.DISCONNECTED, ServerConnectionChangedReason.OTHER_SIDE_TERMINATED); }
@Override
public void onConnected(TCPSocket socket) { updateStatus(ServerConnectionStatus.CONNECTED, ServerConnectionChangedReason.NONE); }
});
this.socket = socket;
waitForConnect(timeout);
return true;
} catch (IOException e) {
updateStatus(ServerConnectionStatus.DISCONNECTED, getReason(e.getMessage()));
socket.disconnect();
}
return false;
}
/**
* Attempts to disconnect from the server with the specified reason. Before this socket is
* closed, it will send a HoloConnectionStopped packet to notify the remote server.
* @param reason the reason for disconnecting
* @return TRUE if successfully disconnected, FALSE on error
*/
public boolean disconnect(ConnectionStoppedReason reason) {
ServerConnectionStatus status = this.status.get();
TCPSocket socket = this.socket;
if (socket == null)
return true;
switch (status) {
case CONNECTING:
case DISCONNECTING:
case DISCONNECTED:
default:
return socket.disconnect();
case CONNECTED:
updateStatus(ServerConnectionStatus.DISCONNECTING, ServerConnectionChangedReason.CLIENT_DISCONNECT);
send(new HoloConnectionStopped(reason).encode().array());
return true;
}
}
/**
* Attempts to send a byte array to the remote server. This method blocks until it has
* completely sent or has failed.
* @param raw the byte array to send
* @return TRUE on success, FALSE on failure
*/
public boolean send(byte [] raw) {
TCPSocket socket = this.socket;
if (socket != null)
return socket.send(swgProtocol.assemble(raw).getBuffer()) > 0;
return false;
}
/**
* Attempts to receive a packet from the remote server. This method blocks until a packet is
* recieved or has failed.
* @return the RawPacket containing the CRC of the SWG message and the raw data array, or NULL
* on error
*/
public RawPacket receive() {
try {
return inboundQueue.take();
} catch (InterruptedException e) {
return null;
}
}
/**
* Returns whether or not there is a packet ready to be received without blocking
* @return TRUE if there is a packet, FALSE otherwise
*/
public boolean hasPacket() {
return !inboundQueue.isEmpty();
}
private void waitForConnect(int timeout) throws IOException {
Socket rawSocket = socket.getSocket();
rawSocket.setSoTimeout(timeout);
try {
socket.startConnection();
send(new HoloSetProtocolVersion(HolocoreProtocol.VERSION).encode().array());
while (isConnecting() && !Delay.isInterrupted()) {
Delay.sleepMilli(50);
}
} finally {
rawSocket.setSoTimeout(0); // Reset back to how it was before the function
}
}
private void handlePacket(int crc, byte [] raw) {
if (crc == HoloConnectionStarted.CRC) {
updateStatus(ServerConnectionStatus.CONNECTED, ServerConnectionChangedReason.NONE);
} else if (crc == HoloConnectionStopped.CRC) {
HoloConnectionStopped packet = new HoloConnectionStopped();
packet.decode(NetBuffer.wrap(raw));
updateStatus(ServerConnectionStatus.DISCONNECTING, ServerConnectionChangedReason.OTHER_SIDE_TERMINATED);
disconnect(packet.getReason());
}
}
private void updateStatus(ServerConnectionStatus status, ServerConnectionChangedReason reason) {
ServerConnectionStatus old = this.status.getAndSet(status);
if (old != status && callback != null)
callback.onConnectionStatusChanged(old, status, reason);
}
private ServerConnectionChangedReason getReason(String message) {
message = message.toLowerCase(Locale.US);
if (message.contains("broken pipe"))
return ServerConnectionChangedReason.BROKEN_PIPE;
if (message.contains("connection reset"))
return ServerConnectionChangedReason.CONNECTION_RESET;
if (message.contains("connection refused"))
return ServerConnectionChangedReason.CONNECTION_REFUSED;
if (message.contains("address in use"))
return ServerConnectionChangedReason.ADDR_IN_USE;
if (message.contains("socket closed"))
return ServerConnectionChangedReason.SOCKET_CLOSED;
if (message.contains("no route to host"))
return ServerConnectionChangedReason.NO_ROUTE_TO_HOST;
return ServerConnectionChangedReason.UNKNOWN;
}
public interface StatusChangedCallback {
void onConnectionStatusChanged(ServerConnectionStatus oldStatus, ServerConnectionStatus newStatus, ServerConnectionChangedReason reason);
}
private UDPServer createUDPServer() {
try {
UDPServer server = new UDPServer(new InetSocketAddress(0), 1500, udpInboundQueue::add);
server.bind();
return server;
} catch (SocketException e) {
Log.e(e);
}
return null;
}
private static class TrustingTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) { }
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) { }
@Override
public X509Certificate[] getAcceptedIssuers() { return null; }
}
}

View File

@@ -0,0 +1,289 @@
package com.projectswg.holocore.client
import com.projectswg.common.network.NetBuffer
import com.projectswg.common.network.packets.swg.holo.HoloConnectionStarted
import com.projectswg.common.network.packets.swg.holo.HoloConnectionStopped
import com.projectswg.common.network.packets.swg.holo.HoloConnectionStopped.ConnectionStoppedReason
import com.projectswg.common.network.packets.swg.holo.HoloSetProtocolVersion
import me.joshlarson.jlcommon.concurrency.Delay
import me.joshlarson.jlcommon.log.Log
import java.io.Closeable
import java.io.EOFException
import java.io.IOException
import java.net.*
import java.security.SecureRandom
import java.security.cert.X509Certificate
import java.util.*
import java.util.concurrent.atomic.AtomicReference
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager
class HolocoreSocket @JvmOverloads constructor(addr: InetAddress, port: Int, private val verifyServer: Boolean = true, private val encryptionEnabled: Boolean = true) : Closeable {
private val status: AtomicReference<ServerConnectionStatus> = AtomicReference(ServerConnectionStatus.DISCONNECTED)
private val udpServer = DatagramSocket()
private val socket = createSocket()
private var callback: StatusChangedCallback? = null
private var socketInputStream: HolocoreInputStream? = null
private var socketOutputStream: HolocoreOutputStream? = null
/**
* Returns the remote address this socket is pointing to
* @return the remote address as an InetSocketAddress
*/
var remoteAddress: InetSocketAddress? = null
private set
/**
* Gets the current connection state of the socket
* @return the connection state
*/
val connectionState: ServerConnectionStatus
get() = status.get()
/**
* Returns whether or not this socket is disconnected
* @return TRUE if disconnected, FALSE otherwise
*/
val isDisconnected: Boolean
get() = status.get() == ServerConnectionStatus.DISCONNECTED
/**
* Returns whether or not this socket is connecting
* @return TRUE if connecting, FALSE otherwise
*/
val isConnecting: Boolean
get() = status.get() == ServerConnectionStatus.CONNECTING
/**
* Returns whether or not this socket is connected
* @return TRUE if connected, FALSE otherwise
*/
val isConnected: Boolean
get() = status.get() == ServerConnectionStatus.CONNECTED
/**
* Retrieves the server status via a UDP query, with the default timeout of 2000ms
* @return the server status as a string
*/
val serverStatus: String
get() = getServerStatus(2000)
init {
this.callback = null
this.remoteAddress = InetSocketAddress(addr, port)
}
/**
* Shuts down any miscellaneous resources--such as the query UDP server
*/
override fun close() {
udpServer.close()
disconnect(ConnectionStoppedReason.APPLICATION)
Log.t("Disconnecting from Holocore")
val socket = this.socket
socketOutputStream?.close()
socketInputStream?.close()
socket.close()
socketOutputStream = null
socketInputStream = null
}
/**
* Shuts down any miscellaneous resources--such as the query UDP server
*/
@Deprecated("should be replaced with close()", replaceWith=ReplaceWith("close()"))
fun terminate() {
close()
}
/**
* Sets a callback for when the status of the server socket changes
* @param callback the callback
*/
fun setStatusChangedCallback(callback: StatusChangedCallback) {
this.callback = callback
}
/**
* Sets the remote address this socket will attempt to connect to
* @param addr the destination address
* @param port the destination port
*/
fun setRemoteAddress(addr: InetAddress, port: Int) {
this.remoteAddress = InetSocketAddress(addr, port)
}
/**
* Retrives the server status via a UDP query, with the specified timeout
* @param timeout the timeout in milliseconds
* @return the server status as a string
*/
fun getServerStatus(timeout: Long): String {
Log.t("Requesting server status from %s", remoteAddress!!)
udpServer.send(DatagramPacket(byteArrayOf(1), 1, remoteAddress!!))
val packet = DatagramPacket(ByteArray(512), 512)
return try {
udpServer.soTimeout = timeout.toInt()
udpServer.receive(packet)
udpServer.soTimeout = 0
val data = NetBuffer.wrap(packet.data)
data.byte
data.ascii // status string
} catch (e: Throwable) {
"OFFLINE" // status string = OFFLINE
}
}
/**
* Attempts to connect to the remote server. This call is a blocking function that will not
* return until it has either successfully connected or has failed with an exception. It
* starts by initializing a TCP connection, then initializes the Holocore connection, then
* returns.
* @param timeout the timeout for the connect call
*/
fun connect(timeout: Int) {
try {
Log.t("Connecting to Holocore: encryption=%b", encryptionEnabled)
socket.connect(remoteAddress ?: throw IllegalStateException("no remote endpoint defined"))
socketInputStream = HolocoreInputStream(socket.getInputStream())
socketOutputStream = HolocoreOutputStream(socket.getOutputStream())
updateStatus(ServerConnectionStatus.CONNECTING, ServerConnectionChangedReason.NONE)
waitForConnect(timeout)
} catch (e: Throwable) {
updateStatus(ServerConnectionStatus.DISCONNECTED, getReason(e.message))
socket.close()
throw e
}
}
/**
* Attempts to disconnect from the server with the specified reason. Before this socket is
* closed, it will send a HoloConnectionStopped packet to notify the remote server.
* @param reason the reason for disconnecting
* @return TRUE if successfully disconnected, FALSE on error
*/
fun disconnect(reason: ConnectionStoppedReason): Boolean {
when (status.get()) {
ServerConnectionStatus.CONNECTING, ServerConnectionStatus.DISCONNECTING, ServerConnectionStatus.DISCONNECTED -> socket.close()
ServerConnectionStatus.CONNECTED -> {
updateStatus(ServerConnectionStatus.DISCONNECTING, ServerConnectionChangedReason.CLIENT_DISCONNECT)
send(HoloConnectionStopped(reason).encode().array())
}
else -> socket.close()
}
return true
}
/**
* Attempts to send a byte array to the remote server. This method blocks until it has
* completely sent or has failed.
* @param raw the byte array to send
* @return TRUE on success, FALSE on failure
*/
fun send(raw: ByteArray): Boolean {
try {
socketOutputStream?.write(raw)
return true
} catch (e: IOException) {
return false
}
}
/**
* Attempts to receive a packet from the remote server. This method blocks until a packet is
* recieved or has failed.
* @return the RawPacket containing the CRC of the SWG message and the raw data array, or NULL
* on error
*/
fun receive(): RawPacket? {
try {
val packet = socketInputStream?.read() ?: return null
handlePacket(packet.crc, packet.data)
return packet
} catch (e: InterruptedException) {
return null
} catch (e: EOFException) {
return null
}
}
private fun waitForConnect(timeout: Int) {
socket.soTimeout = timeout
try {
send(HoloSetProtocolVersion(HolocoreProtocol.VERSION).encode().array())
while (isConnecting && !Delay.isInterrupted()) {
receive() ?: throw IOException("socket closed")
}
} finally {
socket.soTimeout = 0 // Reset back to how it was before the function
}
}
private fun handlePacket(crc: Int, raw: ByteArray) {
when (crc) {
HoloConnectionStarted.CRC -> updateStatus(ServerConnectionStatus.CONNECTED, ServerConnectionChangedReason.NONE)
HoloConnectionStopped.CRC -> {
val packet = HoloConnectionStopped()
packet.decode(NetBuffer.wrap(raw))
updateStatus(ServerConnectionStatus.DISCONNECTING, ServerConnectionChangedReason.OTHER_SIDE_TERMINATED)
disconnect(packet.reason)
}
}
}
private fun createSocket(): Socket {
if (encryptionEnabled) {
val sslContext = SSLContext.getInstance("TLSv1.3")
val tm = if (verifyServer) null else arrayOf<TrustManager>(TrustingTrustManager())
sslContext.init(null, tm, SecureRandom())
return sslContext.socketFactory.createSocket()
} else {
return Socket()
}
}
private fun updateStatus(status: ServerConnectionStatus, reason: ServerConnectionChangedReason) {
val old = this.status.getAndSet(status)
if (old != status && callback != null)
callback!!.onConnectionStatusChanged(old, status, reason)
}
private fun getReason(message: String?): ServerConnectionChangedReason {
var messageLower = message ?: return ServerConnectionChangedReason.UNKNOWN
messageLower = messageLower.toLowerCase(Locale.US)
if (messageLower.contains("broken pipe"))
return ServerConnectionChangedReason.BROKEN_PIPE
if (messageLower.contains("connection reset"))
return ServerConnectionChangedReason.CONNECTION_RESET
if (messageLower.contains("connection refused"))
return ServerConnectionChangedReason.CONNECTION_REFUSED
if (messageLower.contains("address in use"))
return ServerConnectionChangedReason.ADDR_IN_USE
if (messageLower.contains("socket closed"))
return ServerConnectionChangedReason.SOCKET_CLOSED
return if (messageLower.contains("no route to host")) ServerConnectionChangedReason.NO_ROUTE_TO_HOST else ServerConnectionChangedReason.UNKNOWN
}
interface StatusChangedCallback {
fun onConnectionStatusChanged(oldStatus: ServerConnectionStatus, newStatus: ServerConnectionStatus, reason: ServerConnectionChangedReason)
}
private class TrustingTrustManager : X509TrustManager {
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {}
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {}
override fun getAcceptedIssuers(): Array<X509Certificate>? {
return null
}
}
}

View File

@@ -1,41 +1,43 @@
/***********************************************************************************
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
* *
* *
* This file is part of the ProjectSWG Launcher. *
* *
* *
* This program 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. *
* *
* *
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
* along with this program. If not, see <https:></https:>//www.gnu.org/licenses/>. *
* *
*/
package com.projectswg.holocore.client;
package com.projectswg.holocore.client
public class RawPacket {
data class RawPacket(val crc: Int, val data: ByteArray) {
private final int crc;
private final byte[] data;
public RawPacket(int crc, byte[] data) {
this.crc = crc;
this.data = data;
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as RawPacket
if (crc != other.crc) return false
if (!data.contentEquals(other.data)) return false
return true
}
public int getCrc() {
return crc;
}
public byte[] getData() {
return data;
override fun hashCode(): Int {
var result = crc
result = 31 * result + data.contentHashCode()
return result
}
}

View File

@@ -1,40 +0,0 @@
package com.projectswg.holocore.client;
import com.projectswg.common.network.NetBuffer;
import java.nio.ByteBuffer;
class SWGProtocol {
private final HolocoreProtocol holocore;
public SWGProtocol() {
holocore = new HolocoreProtocol();
}
public void reset() {
holocore.reset();
}
public NetBuffer assemble(byte [] packet) {
return holocore.assemble(packet);
}
public boolean addToBuffer(ByteBuffer data) {
return holocore.addToBuffer(data);
}
public RawPacket disassemble() {
byte [] packet = holocore.disassemble();
if (packet.length < 6)
return null;
NetBuffer data = NetBuffer.wrap(packet);
data.getShort();
return new RawPacket(data.getInt(), packet);
}
public boolean hasPacket() {
return holocore.hasPacket();
}
}

View File

@@ -0,0 +1,44 @@
package com.projectswg.holocore.client
import com.projectswg.common.network.NetBuffer
import java.nio.ByteBuffer
internal class SWGProtocol {
private val holocore: HolocoreProtocol = HolocoreProtocol()
fun reset() {
holocore.reset()
}
fun assemble(packet: ByteArray): NetBuffer {
return holocore.assemble(packet)
}
fun addToBuffer(data: ByteArray): Boolean {
return holocore.addToBuffer(data)
}
fun addToBuffer(data: ByteArray, offset: Int, length: Int): Boolean {
return holocore.addToBuffer(data, offset, length)
}
fun addToBuffer(data: ByteBuffer): Boolean {
return holocore.addToBuffer(data)
}
fun disassemble(): RawPacket? {
val packet = holocore.disassemble()
if (packet.size < 6)
return null
val data = NetBuffer.wrap(packet)
data.position(2)
return RawPacket(data.int, packet)
}
fun hasPacket(): Boolean {
return holocore.hasPacket()
}
}

View File

@@ -1,6 +1,6 @@
package com.projectswg.holocore.client;
package com.projectswg.holocore.client
public enum ServerConnectionChangedReason {
enum class ServerConnectionChangedReason {
NONE,
CLIENT_DISCONNECT,
SOCKET_CLOSED,

View File

@@ -1,8 +0,0 @@
package com.projectswg.holocore.client;
public enum ServerConnectionStatus {
CONNECTING,
CONNECTED,
DISCONNECTING,
DISCONNECTED
}

View File

@@ -0,0 +1,8 @@
package com.projectswg.holocore.client
enum class ServerConnectionStatus {
CONNECTING,
CONNECTED,
DISCONNECTING,
DISCONNECTED
}

View File

@@ -1,6 +1,10 @@
module com.projectswg.holocore.client {
requires com.projectswg.common;
requires org.jetbrains.annotations;
requires me.joshlarson.jlcommon;
requires me.joshlarson.jlcommon.network;
requires com.projectswg.common;
requires kotlin.stdlib;
requires kotlinx.coroutines.core;
exports com.projectswg.holocore.client;
}