diff --git a/.idea/misc.xml b/.idea/misc.xml index 239935e..61efd00 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -4,7 +4,7 @@ - + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..2b63946 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index fdb5b8f..9f55a08 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,7 +17,7 @@ repositories { kotlin { jvm { - jvmToolchain(11) + jvmToolchain(8) withJava() testRuns.named("test") { executionTask.configure { diff --git a/src/commonMain/kotlin/net/sergeych/kiloparsec/adapter/NetworkProvider.kt b/src/commonMain/kotlin/net/sergeych/kiloparsec/adapter/NetworkProvider.kt new file mode 100644 index 0000000..9742015 --- /dev/null +++ b/src/commonMain/kotlin/net/sergeych/kiloparsec/adapter/NetworkProvider.kt @@ -0,0 +1,37 @@ +package net.sergeych.kiloparsec.adapter + +import kotlinx.coroutines.channels.ReceiveChannel + +/** + * Multiplatform implementation of an internet address. + * Notice to implementors. It must provide correct and effective [equals] and [hashCode]. + */ +interface NetworkAddress { + val host: String + val port: Int +} + +/** + * Multiplatform datagram abstraction + */ +interface Datagram { + val message: UByteArray + val address: NetworkAddress + suspend fun respondWith(message: UByteArray) +} +interface DatagramReceiver { + + val incoming: ReceiveChannel + suspend fun send(message: UByteArray, networkAddress: NetworkAddress) + @Suppress("unused") + suspend fun send(message: UByteArray, datagramAddress: String) { + send(message, networkAddressOf(datagramAddress)) + } + + suspend fun send(message: UByteArray,host: String,port: Int) = + send(message, NetworkAddress(host,port)) + fun close() +} + +expect fun networkAddressOf(address: String): NetworkAddress +expect fun NetworkAddress(host: String,port: Int): NetworkAddress diff --git a/src/jsMain/kotlin/net/sergeych/kiloparsec/adapter/DatagramProvider.js.kt b/src/jsMain/kotlin/net/sergeych/kiloparsec/adapter/DatagramProvider.js.kt new file mode 100644 index 0000000..ff55b73 --- /dev/null +++ b/src/jsMain/kotlin/net/sergeych/kiloparsec/adapter/DatagramProvider.js.kt @@ -0,0 +1,5 @@ +package net.sergeych.kiloparsec.adapter + +actual fun networkAddressOf(address: String): NetworkAddress { + TODO("Not yet implemented") +} \ No newline at end of file diff --git a/src/jsMain/kotlin/net/sergeych/kiloparsec/adapter/NetworkProvider.js.kt b/src/jsMain/kotlin/net/sergeych/kiloparsec/adapter/NetworkProvider.js.kt new file mode 100644 index 0000000..d4dc4ab --- /dev/null +++ b/src/jsMain/kotlin/net/sergeych/kiloparsec/adapter/NetworkProvider.js.kt @@ -0,0 +1,5 @@ +package net.sergeych.kiloparsec.adapter + +actual fun NetworkAddress(host: String, port: Int): NetworkAddress { + TODO("Not yet implemented") +} \ No newline at end of file diff --git a/src/jvmMain/kotlin/net/sergeych/kiloparsec/adapter/DatagramProvider.jvm.kt b/src/jvmMain/kotlin/net/sergeych/kiloparsec/adapter/DatagramProvider.jvm.kt new file mode 100644 index 0000000..7a8cea4 --- /dev/null +++ b/src/jvmMain/kotlin/net/sergeych/kiloparsec/adapter/DatagramProvider.jvm.kt @@ -0,0 +1,8 @@ +package net.sergeych.kiloparsec.adapter + +import java.net.InetAddress + +actual fun networkAddressOf(address: String): NetworkAddress { + val (host,port) = address.split(":") + return JvmNetworkAddress(InetAddress.getByName(host), port.toInt()) +} \ No newline at end of file diff --git a/src/jvmMain/kotlin/net/sergeych/kiloparsec/adapter/NetworkProvider.jvm.kt b/src/jvmMain/kotlin/net/sergeych/kiloparsec/adapter/NetworkProvider.jvm.kt new file mode 100644 index 0000000..6de79e8 --- /dev/null +++ b/src/jvmMain/kotlin/net/sergeych/kiloparsec/adapter/NetworkProvider.jvm.kt @@ -0,0 +1,6 @@ +package net.sergeych.kiloparsec.adapter + +import java.net.InetAddress + +actual fun NetworkAddress(host: String, port: Int): NetworkAddress = + JvmNetworkAddress(InetAddress.getByName(host), port) diff --git a/src/jvmMain/kotlin/net/sergeych/kiloparsec/adapter/UdpServer.kt b/src/jvmMain/kotlin/net/sergeych/kiloparsec/adapter/UdpServer.kt new file mode 100644 index 0000000..4766d0f --- /dev/null +++ b/src/jvmMain/kotlin/net/sergeych/kiloparsec/adapter/UdpServer.kt @@ -0,0 +1,135 @@ +package net.sergeych.kiloparsec.adapter + +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import net.sergeych.mp_logger.LogTag +import net.sergeych.mp_logger.exception +import net.sergeych.mp_logger.info +import net.sergeych.mp_logger.warning +import java.net.DatagramPacket +import java.net.DatagramSocket +import java.net.InetAddress +import java.util.concurrent.atomic.AtomicInteger + +private val counter = AtomicInteger(0) + +class JvmNetworkAddress(val inetAddress: InetAddress, override val port: Int) : NetworkAddress { + override val host: String by lazy { inetAddress.hostName } + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is JvmNetworkAddress) return false + + if (inetAddress != other.inetAddress) return false + if (port != other.port) return false + + return true + } + + override fun hashCode(): Int { + var result = inetAddress.hashCode() + result = 31 * result + port + return result + } + +} + +class UdpDatagram(override val message: UByteArray, val inetAddress: InetAddress, val port: Int) : Datagram { + + override val address: NetworkAddress by lazy { + JvmNetworkAddress(inetAddress, port) + } + + private val access = Mutex() + + private var socket: DatagramSocket? = null + override suspend fun respondWith(message: UByteArray) { + withContext(Dispatchers.IO) { + access.withLock { + if (socket == null) socket = DatagramSocket() + val packet = DatagramPacket( + message.toByteArray(), + message.size, + inetAddress, + port + ) + socket!!.send(packet) + } + } + } +} + +@OptIn(DelicateCoroutinesApi::class) +class UdpServer(val port: Int) : + DatagramReceiver, LogTag("UDPS:${counter.incrementAndGet()}") { + private var isClosed = false + + + private val deferredSocket = CompletableDeferred() + private var job: Job? = null + + private suspend fun start() = try { + coroutineScope { + val socket = DatagramSocket(port) + val buffer = ByteArray(16384) + val packet = DatagramPacket(buffer, buffer.size) + deferredSocket.complete(socket) + while (isActive && !isClosed) { + try { + socket.receive(packet) + val data = packet.data.sliceArray(0..(2048, BufferOverflow.DROP_OLDEST) + override val incoming = channel + + override suspend fun send(message: UByteArray, networkAddress: NetworkAddress) { + networkAddress as JvmNetworkAddress + withContext(Dispatchers.IO) { + val packet = DatagramPacket( + message.toByteArray(), message.size, + networkAddress.inetAddress, networkAddress.port + ) + deferredSocket.await().send(packet) + } + } +} \ No newline at end of file diff --git a/src/jvmTest/kotlin/net/sergeych/kiloparsec/adapters/UServerTest.kt b/src/jvmTest/kotlin/net/sergeych/kiloparsec/adapters/UServerTest.kt new file mode 100644 index 0000000..102b58b --- /dev/null +++ b/src/jvmTest/kotlin/net/sergeych/kiloparsec/adapters/UServerTest.kt @@ -0,0 +1,26 @@ +package net.sergeych.kiloparsec.adapters + +import com.ionspin.kotlin.crypto.util.encodeToUByteArray +import kotlinx.coroutines.test.runTest +import net.sergeych.kiloparsec.adapter.UdpServer +import net.sergeych.mp_logger.Log +import org.junit.jupiter.api.Assertions.assertEquals +import kotlin.test.Test + +class UServerTest { + + @Test + fun udpProvider() = runTest { + Log.connectConsole(Log.Level.DEBUG) + val s1 = UdpServer(17120) + val s2 = UdpServer(17121) + s1.send("Hello".encodeToUByteArray(), "localhost",17121) + val d1 = s2.incoming.receive() + assertEquals(d1.address.port, 17120) + assertEquals("Hello", d1.message.toByteArray().decodeToString()) + d1.respondWith("world".encodeToUByteArray()) + assertEquals("world", s1.incoming.receive().message.toByteArray().decodeToString()) + // println("s1: ${s1.bindAddress()}") + + } +} \ No newline at end of file diff --git a/src/nativeMain/kotlin/net/sergeych/kiloparsec/adapter/DatagramProvider.native.kt b/src/nativeMain/kotlin/net/sergeych/kiloparsec/adapter/DatagramProvider.native.kt new file mode 100644 index 0000000..ff55b73 --- /dev/null +++ b/src/nativeMain/kotlin/net/sergeych/kiloparsec/adapter/DatagramProvider.native.kt @@ -0,0 +1,5 @@ +package net.sergeych.kiloparsec.adapter + +actual fun networkAddressOf(address: String): NetworkAddress { + TODO("Not yet implemented") +} \ No newline at end of file diff --git a/src/nativeMain/kotlin/net/sergeych/kiloparsec/adapter/NetworkProvider.native.kt b/src/nativeMain/kotlin/net/sergeych/kiloparsec/adapter/NetworkProvider.native.kt new file mode 100644 index 0000000..d4dc4ab --- /dev/null +++ b/src/nativeMain/kotlin/net/sergeych/kiloparsec/adapter/NetworkProvider.native.kt @@ -0,0 +1,5 @@ +package net.sergeych.kiloparsec.adapter + +actual fun NetworkAddress(host: String, port: Int): NetworkAddress { + TODO("Not yet implemented") +} \ No newline at end of file