diff --git a/client/src/main/kotlin/org/hso/texturesyncclient/controller/net/Connection.kt b/client/src/main/kotlin/org/hso/texturesyncclient/controller/net/Connection.kt new file mode 100644 index 0000000..f6c2c6a --- /dev/null +++ b/client/src/main/kotlin/org/hso/texturesyncclient/controller/net/Connection.kt @@ -0,0 +1,65 @@ +package org.hso.texturesyncclient.controller.net + +import com.google.gson.* +import java.io.* +import java.lang.Exception +import java.net.* + +@Suppress("MemberVisibilityCanBePrivate") +class Connection(val addr: InetAddress, val port: Int = 10796) : Closeable { + + var socket: Socket? = null + var output: DataOutputStream? = null + var input: DataInputStream? = null + + @Throws(IOException::class) + private fun getStreams(): Pair { + val i: DataInputStream + val o: DataOutputStream + + if (socket == null || !socket!!.isConnected) { + val sock = Socket(addr, port) + i = DataInputStream(BufferedInputStream(sock.getInputStream())) + o = DataOutputStream(BufferedOutputStream(sock.getOutputStream())) + + input = i + output = o + socket = sock + } else { + i = input!! + o = output!! + } + + return Pair(i, o) + } + + @Throws(IOException::class) + fun ping() { + val io = getStreams() + + val obj = JsonObject() + obj.add("ping", JsonObject()) + + JsonPacket(obj).write(io.second) + + when (Packet.read(io.first)) { + is JsonPacket -> return + is ErrorPacket -> throw Exception("TODO") + is BinaryPacket -> throw Exception("TODO") + } + } + + @Throws(IOException::class) + override fun close() { + if (output != null) { + output!!.close() + } + if (input != null) { + input!!.close() + } + if (socket != null) { + socket!!.close() + } + } + +} \ No newline at end of file diff --git a/client/src/main/kotlin/org/hso/texturesyncclient/controller/net/Packet.kt b/client/src/main/kotlin/org/hso/texturesyncclient/controller/net/Packet.kt new file mode 100644 index 0000000..4b6b531 --- /dev/null +++ b/client/src/main/kotlin/org/hso/texturesyncclient/controller/net/Packet.kt @@ -0,0 +1,167 @@ +package org.hso.texturesyncclient.controller.net + +import com.google.gson.JsonObject +import com.google.gson.JsonParseException +import com.google.gson.JsonParser +import java.io.DataInputStream +import java.io.DataOutputStream +import java.io.IOException +import java.lang.Exception + +open class PacketException(msg: String) : Exception(msg) +class PacketTooLongException : PacketException("The Package is too long.") +class PacketInvalidType : PacketException("The Package has an Invalid Type.") +class PacketInvalidData : PacketException("The Package has an Invalid Data. (e.g. Invalid Json.)") + +abstract class Packet { + + @Throws(IOException::class) + abstract fun write(out: DataOutputStream) + + companion object { + + const val TYPE_ERROR: Byte = 0 + protected const val TYPE_ERROR_MAX_PAYLOAD: Int = 1024 + + const val TYPE_JSON: Byte = 1 + protected const val TYPE_JSON_MAX_PAYLOAD: Int = 16 * 1024 * 1024 + + const val TYPE_BINARY: Byte = 2 + protected const val TYPE_BINARY_MAX_PAYLOAD: Int = 512 * 1024 * 1024 + + @Throws(PacketException::class, IOException::class) + fun read(input: DataInputStream): Packet { + // Type Byte + val type = input.readByte() + + // 3 Reserved Bytes + input.readByte() + input.readByte() + input.readByte() + + // 4 Len Bytes + val length = input.readInt() + + when (type) { + TYPE_ERROR -> if (length > TYPE_ERROR_MAX_PAYLOAD) { + throw PacketTooLongException() + } + + TYPE_JSON -> if (length > TYPE_JSON_MAX_PAYLOAD) { + throw PacketTooLongException() + } + + TYPE_BINARY -> if (length > TYPE_BINARY_MAX_PAYLOAD) { + throw PacketTooLongException() + } + + else -> throw PacketInvalidType() + } + + val payload = ByteArray(length) + input.readFully(payload) + + when (type) { + TYPE_ERROR -> { + val msg = String(payload) + val results = msg.split(' ', ignoreCase = false, limit = 2) + + return if (results.size == 2) { + val code = results[0].toIntOrNull() + + if (code == null) { + ErrorPacket(0, msg) + } else { + ErrorPacket(code, results[1]) + } + } else { + ErrorPacket(0, msg) + } + } + + TYPE_JSON -> { + try { + val obj = JsonParser().parse(String(payload)).asJsonObject + + return JsonPacket(obj) + } catch (e: JsonParseException) { + throw PacketInvalidData() + } + } + + TYPE_BINARY -> { + return BinaryPacket(payload) + } + + else -> { + // Unreachable + throw PacketInvalidType() + } + } + } + } + +} + +data class JsonPacket(val content: JsonObject) : Packet() { + override fun write(out: DataOutputStream) { + val payload = content.toString().toByteArray() + // Tag Byte + out.writeByte(TYPE_JSON.toInt()) + + // 3 Reserved Bytes + out.writeByte(0x42) + out.writeByte(0x42) + out.writeByte(0x42) + + // Length of Payload + out.writeInt(payload.size) + + // Payload + out.write(payload) + + out.flush() + } +} + +@Suppress("MemberVisibilityCanBePrivate") +class BinaryPacket(val content: ByteArray) : Packet() { + override fun write(out: DataOutputStream) { + // Tag Byte + out.writeByte(TYPE_BINARY.toInt()) + + // 3 Reserved Bytes + out.writeByte(0x42) + out.writeByte(0x42) + out.writeByte(0x42) + + // Length of Payload + out.writeInt(content.size) + + // Payload + out.write(content) + + out.flush() + } +} + +data class ErrorPacket(val code: Int, val message: String) : Packet() { + override fun write(out: DataOutputStream) { + val payload = "$code $message".toByteArray() + // Tag Byte + out.writeByte(TYPE_ERROR.toInt()) + + // 3 Reserved Bytes + out.writeByte(0x42) + out.writeByte(0x42) + out.writeByte(0x42) + + // Length of Payload + out.writeInt(payload.size) + + // Payload + out.write(payload) + + out.flush() + } +} \ No newline at end of file diff --git a/client/src/main/kotlin/org/hso/texturesyncclient/controller/net/test.kt b/client/src/main/kotlin/org/hso/texturesyncclient/controller/net/test.kt new file mode 100644 index 0000000..1a1f0c9 --- /dev/null +++ b/client/src/main/kotlin/org/hso/texturesyncclient/controller/net/test.kt @@ -0,0 +1,13 @@ +package org.hso.texturesyncclient.controller.net + +import java.net.* + +fun main() { + // Just some test code. + + val con = Connection(InetAddress.getByName("::1")) + + con.ping() + + con.close() +} \ No newline at end of file