diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 0c6a177a319..5098d607e20 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -23,7 +23,6 @@ kotlin { } nonJvmMain { dependencies { - implementation(libs.ktor.utils) implementation(libs.bignum) implementation(libs.stately.collections) } diff --git a/common/src/nonJvmMain/kotlin/DiscordBitSet.kt b/common/src/nonJvmMain/kotlin/DiscordBitSet.kt index c883976ebf8..f1764a24a20 100644 --- a/common/src/nonJvmMain/kotlin/DiscordBitSet.kt +++ b/common/src/nonJvmMain/kotlin/DiscordBitSet.kt @@ -2,14 +2,25 @@ package dev.kord.common import com.ionspin.kotlin.bignum.integer.BigInteger import com.ionspin.kotlin.bignum.integer.Sign -import io.ktor.utils.io.core.* -internal actual fun formatIntegerFromLittleEndianLongArray(data: LongArray) = - withBuffer(data.size * Long.SIZE_BYTES) { - // need to convert from little-endian data to big-endian expected by BigInteger - writeFully(data.reversedArray()) - BigInteger.fromByteArray(readBytes(), Sign.POSITIVE).toString() +internal actual fun formatIntegerFromLittleEndianLongArray(data: LongArray): String { + // need to convert from little-endian data to big-endian expected by BigInteger + val bytes = ByteArray(size = data.size * Long.SIZE_BYTES) + val lastIndex = data.lastIndex + for (i in 0..lastIndex) { + val offset = (lastIndex - i) * Long.SIZE_BYTES + val long = data[i] + bytes[offset] = (long ushr 56).toByte() + bytes[offset + 1] = (long ushr 48).toByte() + bytes[offset + 2] = (long ushr 40).toByte() + bytes[offset + 3] = (long ushr 32).toByte() + bytes[offset + 4] = (long ushr 24).toByte() + bytes[offset + 5] = (long ushr 16).toByte() + bytes[offset + 6] = (long ushr 8).toByte() + bytes[offset + 7] = long.toByte() } + return BigInteger.fromByteArray(bytes, Sign.POSITIVE).toString() +} internal actual fun parseNonNegativeIntegerToBigEndianByteArray(value: String): ByteArray = BigInteger .parseString(value) diff --git a/gateway/build.gradle.kts b/gateway/build.gradle.kts index 81cedadaca2..076873ffa47 100644 --- a/gateway/build.gradle.kts +++ b/gateway/build.gradle.kts @@ -10,7 +10,6 @@ kotlin { api(projects.common) api(libs.bundles.ktor.client.serialization) - api(libs.ktor.client.websockets) implementation(libs.kotlin.logging) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7e0b2b54d92..ab485a8dfde 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,10 +1,10 @@ [versions] # api dependencies -kotlin = "2.0.20" # https://github.com/JetBrains/kotlin -ktor = "2.3.12" # https://github.com/ktorio/ktor -kotlinx-coroutines = "1.8.1" # https://github.com/Kotlin/kotlinx.coroutines -kotlinx-serialization = "1.7.2" # https://github.com/Kotlin/kotlinx.serialization +kotlin = "2.0.21" # https://github.com/JetBrains/kotlin +ktor = "3.0.0" # https://github.com/ktorio/ktor +kotlinx-coroutines = "1.9.0" # https://github.com/Kotlin/kotlinx.coroutines +kotlinx-serialization = "1.7.3" # https://github.com/Kotlin/kotlinx.serialization kotlinx-datetime = "0.6.1" # https://github.com/Kotlin/kotlinx-datetime kord-cache = "0.5.4" # https://github.com/kordlib/cache @@ -12,26 +12,26 @@ kord-cache = "0.5.4" # https://github.com/kordlib/cache kotlin-logging = "7.0.0" # https://github.com/oshai/kotlin-logging kotlin-logging-old = "3.0.5" # TODO remove after dependency is removed in rest, gateway, voice and core slf4j = "2.0.16" # https://www.slf4j.org -kotlin-node = "20.14.10-pre.800" # https://github.com/JetBrains/kotlin-wrappers +kotlin-node = "22.5.4-pre.818" # https://github.com/JetBrains/kotlin-wrappers bignum = "0.3.10" # https://github.com/ionspin/kotlin-multiplatform-bignum stately = "2.1.0" # https://github.com/touchlab/Stately fastZlib = "2.0.1" # https://github.com/timotejroiko/fast-zlib # code generation -ksp = "2.0.20-1.0.24" # https://github.com/google/ksp +ksp = "2.0.21-1.0.25" # https://github.com/google/ksp kotlinpoet = "1.18.1" # https://github.com/square/kotlinpoet # tests -junit-jupiter = "5.11.0" # https://github.com/junit-team/junit5 -junit-platform = "1.11.0" -mockk = "1.13.12" # https://github.com/mockk/mockk +junit-jupiter = "5.11.2" # https://github.com/junit-team/junit5 +junit-platform = "1.11.2" +mockk = "1.13.13" # https://github.com/mockk/mockk kbson = "0.4.0" # https://github.com/mongodb/kbson # plugins dokka = "2.0.0-Beta" # https://github.com/Kotlin/dokka kotlinx-atomicfu = "0.25.0" # https://github.com/Kotlin/kotlinx-atomicfu binary-compatibility-validator = "0.16.3" # https://github.com/Kotlin/binary-compatibility-validator -buildconfig = "5.4.0" # https://github.com/gmazzo/gradle-buildconfig-plugin +buildconfig = "5.5.0" # https://github.com/gmazzo/gradle-buildconfig-plugin [libraries] @@ -46,10 +46,9 @@ ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } ktor-client-js = { module = "io.ktor:ktor-client-js", version.ref = "ktor" } ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" } -ktor-client-websockets = { module = "io.ktor:ktor-client-websockets", version.ref = "ktor" } ktor-client-mock = { module = "io.ktor:ktor-client-mock", version.ref = "ktor" } ktor-network = { module = "io.ktor:ktor-network", version.ref = "ktor" } -ktor-utils = { module = "io.ktor:ktor-utils", version.ref = "ktor" } +ktor-io = { module = "io.ktor:ktor-io", version.ref = "ktor" } # kotlinx kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2b189974c29..fb602ee2af0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=5b9c5eb3f9fc2c94abaea57d90bd78747ca117ddbbf96c859d3741181a12bf2a -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip +distributionSha256Sum=31c55713e40233a8303827ceb42ca48a47267a0ad4bab9177123121e71524c26 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/rest/src/commonMain/kotlin/request/KtorRequestHandler.kt b/rest/src/commonMain/kotlin/request/KtorRequestHandler.kt index f40480c32c4..e94d947ef48 100644 --- a/rest/src/commonMain/kotlin/request/KtorRequestHandler.kt +++ b/rest/src/commonMain/kotlin/request/KtorRequestHandler.kt @@ -9,7 +9,6 @@ import io.ktor.client.* import io.ktor.client.request.* import io.ktor.client.request.forms.* import io.ktor.client.statement.* -import io.ktor.content.TextContent import io.ktor.http.* import io.ktor.http.content.* import kotlinx.datetime.Clock diff --git a/rest/src/commonTest/kotlin/request/MessageRequests.kt b/rest/src/commonTest/kotlin/request/MessageRequests.kt index 6e43c1d9dec..7c728d37cbd 100644 --- a/rest/src/commonTest/kotlin/request/MessageRequests.kt +++ b/rest/src/commonTest/kotlin/request/MessageRequests.kt @@ -8,10 +8,10 @@ import dev.kord.common.entity.Snowflake import dev.kord.common.entity.optional.Optional import dev.kord.rest.json.readFile import dev.kord.rest.service.ChannelService -import dev.kord.test.Platform import io.ktor.client.* import io.ktor.client.engine.mock.* import io.ktor.client.request.forms.* +import io.ktor.utils.io.* import kotlinx.coroutines.test.runTest import kotlinx.datetime.Clock import kotlinx.serialization.encodeToString @@ -62,10 +62,9 @@ class MessageRequests { val channelService = ChannelService(KtorRequestHandler(client = HttpClient(mockEngine), token = "")) - val fileChannel = readFile("images/kord.png") + val fileChannel = readFile("images/kord.png").counted() with(fileChannel) { - if (Platform.IS_JVM) assertFalse(isClosedForWrite) // only read lazily on jvm assertFalse(isClosedForRead) assertEquals(0L, totalBytesRead) @@ -74,7 +73,6 @@ class MessageRequests { } assertEquals(mockMessage, createdMessage) - assertTrue(isClosedForWrite) assertTrue(isClosedForRead) assertTrue(totalBytesRead > 0L) } diff --git a/test-kit/build.gradle.kts b/test-kit/build.gradle.kts index e1357aadebf..a5c13ff37e4 100644 --- a/test-kit/build.gradle.kts +++ b/test-kit/build.gradle.kts @@ -7,7 +7,7 @@ kotlin { commonMain { dependencies { api(libs.bundles.test.common) - api(libs.ktor.utils) + api(libs.ktor.io) } } jsMain { diff --git a/voice/api/voice.api b/voice/api/voice.api index 2bdd226cdc1..6680afe9661 100644 --- a/voice/api/voice.api +++ b/voice/api/voice.api @@ -1232,7 +1232,7 @@ public final class dev/kord/voice/udp/RTPPacket$Builder { } public final class dev/kord/voice/udp/RTPPacket$Companion { - public final fun fromPacket (Lio/ktor/utils/io/core/ByteReadPacket;)Ldev/kord/voice/udp/RTPPacket; + public final fun fromPacket (Lkotlinx/io/Source;)Ldev/kord/voice/udp/RTPPacket; } public final class dev/kord/voice/udp/RTPPacketKt { diff --git a/voice/src/main/kotlin/udp/GlobalVoiceUdpSocket.kt b/voice/src/main/kotlin/udp/GlobalVoiceUdpSocket.kt index d4577e45cf3..4221bbf50d0 100644 --- a/voice/src/main/kotlin/udp/GlobalVoiceUdpSocket.kt +++ b/voice/src/main/kotlin/udp/GlobalVoiceUdpSocket.kt @@ -5,12 +5,13 @@ import io.github.oshai.kotlinlogging.KotlinLogging import io.ktor.network.selector.* import io.ktor.network.sockets.* import io.ktor.utils.io.core.* -import kotlinx.coroutines.CoroutineName -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.flow.* -import kotlin.text.String +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.emitAll +import kotlinx.io.Sink +import kotlinx.io.readString +import kotlinx.io.readUShort private val globalVoiceSocketLogger = KotlinLogging.logger { } @@ -32,18 +33,16 @@ public object GlobalVoiceUdpSocket : VoiceUdpSocket { private val _incoming: MutableSharedFlow = MutableSharedFlow() override val incoming: SharedFlow = _incoming - private val socket = aSocket(ActorSelectorManager(socketScope.coroutineContext)).udp().bind() + private val socket = socketScope.async { + aSocket(ActorSelectorManager(socketScope.coroutineContext)).udp().bind() + } private val EMPTY_DATA = ByteArray(DISCOVERY_DATA_SIZE) init { - socket.incoming - .consumeAsFlow() - .onEach { _incoming.emit(it) } - .launchIn(socketScope) + socketScope.launch { _incoming.emitAll(socket.await().incoming) } } - @OptIn(ExperimentalUnsignedTypes::class) override suspend fun discoverIp(address: InetSocketAddress, ssrc: Int): InetSocketAddress { globalVoiceSocketLogger.trace { "discovering ip" } @@ -51,15 +50,15 @@ public object GlobalVoiceUdpSocket : VoiceUdpSocket { writeShort(REQUEST) writeShort(MESSAGE_LENGTH) writeInt(ssrc) - writeFully(EMPTY_DATA) + write(EMPTY_DATA) }) return with(receiveFrom(address).packet) { require(readShort() == RESPONSE) { "did not receive a response." } - require(readShort() == MESSAGE_LENGTH) { "expected $MESSAGE_LENGTH bytes of data."} - discardExact(4) // ssrc + require(readShort() == MESSAGE_LENGTH) { "expected $MESSAGE_LENGTH bytes of data." } + skip(byteCount = 4) // ssrc - val ip = String(readBytes(64)).trimEnd(0.toChar()) + val ip = readString(byteCount = 64).trimEnd(0.toChar()) val port = readUShort().toInt() InetSocketAddress(ip, port) @@ -67,12 +66,12 @@ public object GlobalVoiceUdpSocket : VoiceUdpSocket { } override suspend fun send(packet: Datagram) { - socket.send(packet) + socket.await().send(packet) } override suspend fun stop() { /* this doesn't stop until the end of the process */ } - private fun packet(address: SocketAddress, builder: BytePacketBuilder.() -> Unit): Datagram { + private fun packet(address: SocketAddress, builder: Sink.() -> Unit): Datagram { return Datagram(buildPacket(block = builder), address) } } diff --git a/voice/src/main/kotlin/udp/RTPPacket.kt b/voice/src/main/kotlin/udp/RTPPacket.kt index 43d734c38f7..56a89078e20 100644 --- a/voice/src/main/kotlin/udp/RTPPacket.kt +++ b/voice/src/main/kotlin/udp/RTPPacket.kt @@ -4,7 +4,10 @@ import dev.kord.voice.io.ByteArrayView import dev.kord.voice.io.MutableByteArrayCursor import dev.kord.voice.io.mutableCursor import dev.kord.voice.io.view -import io.ktor.utils.io.core.* +import kotlinx.io.Source +import kotlinx.io.readByteArray +import kotlinx.io.readUInt +import kotlinx.io.readUShort import kotlin.experimental.and internal const val RTP_HEADER_LENGTH = 12 @@ -38,8 +41,8 @@ public data class RTPPacket( public companion object { internal const val VERSION = 2 - public fun fromPacket(packet: ByteReadPacket): RTPPacket? = with(packet) base@{ - if (remaining <= 13) return@base null + public fun fromPacket(packet: Source): RTPPacket? = with(packet) base@{ + if (!request(byteCount = 14)) return@base null /* * first byte | bit table @@ -72,15 +75,15 @@ public data class RTPPacket( payloadType = this and 0x7F } - val sequence = readShort().toUShort() - val timestamp = readInt().toUInt() - val ssrc = readInt().toUInt() + val sequence = readUShort() + val timestamp = readUInt() + val ssrc = readUInt() // each csrc takes up 4 bytes, plus more data is required - if (remaining <= csrcCount * 4 + 1) return@base null + if (!request(byteCount = csrcCount * 4L + 2)) return@base null val csrcIdentifiers = UIntArray(csrcCount.toInt()) { readUInt() } - val payload = readBytes().view() + val payload = readByteArray().view() val paddingBytes = if (hasPadding) { payload[payload.viewSize - 1] } else 0