From 852cb206af427d3bc6051c2bb56c45ed00610d8a Mon Sep 17 00:00:00 2001 From: Alexander Sysoev Date: Tue, 29 Jul 2025 21:38:03 +0200 Subject: [PATCH] Added jvm codecs implementations --- grpc/grpc-core/build.gradle.kts | 21 +- grpc/grpc-core/gradle.properties | 4 +- .../kotlinx/rpc/grpc/internal/WireDecoder.kt | 6 +- .../kotlinx/rpc/grpc/internal/WireEncoder.kt | 4 +- .../kotlinx/rpc/grpc/internal/readPacked.kt | 49 ++++ .../rpc/grpc/internal/WireCodecTest.kt | 25 +- .../rpc/grpc/internal/readPacked.js.kt | 17 ++ .../rpc/grpc/internal/WireDecoder.jvm.kt | 118 +++++++++- .../rpc/grpc/internal/WireEncoder.jvm.kt | 215 +++++++++++++++++- .../kotlinx/rpc/grpc/internal/WireSize.jvm.kt | 23 +- .../rpc/grpc/internal/readPacked.jvm.kt | 17 ++ .../grpc/core/test/TestReferenceService.kt | 6 + .../src/jvmTest/proto/repeated.proto | 6 +- .../rpc/grpc/internal/WireDecoder.native.kt | 34 +-- .../rpc/grpc/internal/WireEncoder.native.kt | 6 +- .../rpc/grpc/internal/readPacked.native.kt | 24 ++ .../rpc/grpc/internal/WireCodecTest.native.kt | 7 + .../rpc/grpc/internal/readPacked.wasmJs.kt | 17 ++ grpc/grpc-ktor-server/gradle.properties | 2 + .../rpc/protobuf/ModelToKotlinGenerator.kt | 40 +--- 20 files changed, 537 insertions(+), 104 deletions(-) create mode 100644 grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.kt rename grpc/grpc-core/src/{nativeTest => commonTest}/kotlin/kotlinx/rpc/grpc/internal/WireCodecTest.kt (98%) create mode 100644 grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.js.kt create mode 100644 grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.jvm.kt create mode 100644 grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.native.kt create mode 100644 grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/internal/WireCodecTest.native.kt create mode 100644 grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.wasmJs.kt diff --git a/grpc/grpc-core/build.gradle.kts b/grpc/grpc-core/build.gradle.kts index 3bcf08780..47fe51902 100644 --- a/grpc/grpc-core/build.gradle.kts +++ b/grpc/grpc-core/build.gradle.kts @@ -30,6 +30,13 @@ kotlin { implementation(libs.atomicfu) implementation(libs.kotlinx.io.core) + implementation(libs.kotlinx.collections.immutable) + } + } + + commonTest { + dependencies { + implementation(libs.kotlin.test) } } @@ -39,6 +46,7 @@ kotlin { api(libs.grpc.util) api(libs.grpc.stub) api(libs.grpc.protobuf) + api("io.grpc:grpc-protobuf-lite:${libs.versions.grpc.asProvider().get()}") implementation(libs.grpc.kotlin.stub) // causes problems to jpms if api api(libs.protobuf.java.util) implementation(libs.protobuf.kotlin) @@ -50,7 +58,6 @@ kotlin { implementation(projects.grpc.grpcCore) implementation(libs.coroutines.core) implementation(libs.coroutines.test) - implementation(libs.kotlin.test) implementation(libs.grpc.stub) implementation(libs.grpc.netty) @@ -60,18 +67,6 @@ kotlin { implementation(libs.protobuf.kotlin) } } - - nativeMain { - dependencies { - implementation(libs.kotlinx.collections.immutable) - } - } - - nativeTest { - dependencies { - implementation(kotlin("test")) - } - } } val grpcppCLib = projectDir.resolve("../grpcpp-c") diff --git a/grpc/grpc-core/gradle.properties b/grpc/grpc-core/gradle.properties index 9452bb38d..4ae0c96a1 100644 --- a/grpc/grpc-core/gradle.properties +++ b/grpc/grpc-core/gradle.properties @@ -2,6 +2,8 @@ # Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. # kotlinx.rpc.exclude.wasmWasi=true +kotlinx.rpc.exclude.js=true +kotlinx.rpc.exclude.wasmJs=true kotlinx.rpc.exclude.iosArm64=true kotlinx.rpc.exclude.iosX64=true kotlinx.rpc.exclude.iosSimulatorArm64=true @@ -16,4 +18,4 @@ kotlinx.rpc.exclude.watchosArm32=true kotlinx.rpc.exclude.watchosArm64=true kotlinx.rpc.exclude.watchosDeviceArm64=true kotlinx.rpc.exclude.watchosSimulatorArm64=true -kotlinx.rpc.exclude.watchosX64=true \ No newline at end of file +kotlinx.rpc.exclude.watchosX64=true diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/WireDecoder.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/WireDecoder.kt index ece585248..64cbfd1d9 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/WireDecoder.kt +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/WireDecoder.kt @@ -6,6 +6,10 @@ package kotlinx.rpc.grpc.internal import kotlinx.io.Buffer +// TODO: Evaluate if this buffer size is suitable for all targets (KRPC-186) +// maximum buffer size to allocate as contiguous memory in bytes +internal const val MAX_PACKED_BULK_SIZE: Int = 1_000_000 + /** * A platform-specific decoder for wire format data. * @@ -78,4 +82,4 @@ internal interface WireDecoder : AutoCloseable { * * @param source The buffer containing the encoded wire-format data. */ -internal expect fun WireDecoder(source: Buffer): WireDecoder \ No newline at end of file +internal expect fun WireDecoder(source: Buffer): WireDecoder diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/WireEncoder.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/WireEncoder.kt index 918b30bef..ff5b6a725 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/WireEncoder.kt +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/WireEncoder.kt @@ -17,7 +17,7 @@ import kotlinx.io.Sink @OptIn(ExperimentalUnsignedTypes::class) internal interface WireEncoder { fun flush() - fun writeBool(field: Int, value: Boolean): Boolean + fun writeBool(fieldNr: Int, value: Boolean): Boolean fun writeInt32(fieldNr: Int, value: Int): Boolean fun writeInt64(fieldNr: Int, value: Long): Boolean fun writeUInt32(fieldNr: Int, value: UInt): Boolean @@ -51,4 +51,4 @@ internal interface WireEncoder { } -internal expect fun WireEncoder(sink: Sink): WireEncoder \ No newline at end of file +internal expect fun WireEncoder(sink: Sink): WireEncoder diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.kt new file mode 100644 index 000000000..5eb26b278 --- /dev/null +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.internal + +import kotlinx.io.Buffer + +internal expect fun WireDecoder.pushLimit(byteLen: Int): Int +internal expect fun WireDecoder.popLimit(limit: Int) +internal expect fun WireDecoder.bytesUntilLimit(): Int + +internal inline fun WireDecoder.readPackedVarInternal( + crossinline size: () -> Long, + crossinline readFn: () -> T, + crossinline withError: () -> Unit, + crossinline hadError: () -> Boolean, +): List { + val byteLen = readInt32() + if (hadError()) { + return emptyList() + } + if (byteLen < 0) { + return emptyList().apply { withError() } + } + val size = size() + // no size check on jvm + if (size != -1L && size < byteLen) { + return emptyList().apply { withError() } + } + if (byteLen == 0) { + return emptyList() // actually an empty list (no error) + } + + val limit = pushLimit(byteLen) + + val result = mutableListOf() + + while (bytesUntilLimit() > 0) { + val elem = readFn() + if (hadError()) { + break + } + result.add(elem) + } + + popLimit(limit) + return result +} diff --git a/grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/internal/WireCodecTest.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/internal/WireCodecTest.kt similarity index 98% rename from grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/internal/WireCodecTest.kt rename to grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/internal/WireCodecTest.kt index d93132fc1..577b90c42 100644 --- a/grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/internal/WireCodecTest.kt +++ b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/internal/WireCodecTest.kt @@ -4,13 +4,15 @@ package kotlinx.rpc.grpc.internal -import kotlinx.cinterop.ExperimentalForeignApi import kotlinx.io.Buffer -import kotlin.experimental.ExperimentalNativeApi import kotlin.test.* -// TODO: Move this to the commonTest -@OptIn(ExperimentalForeignApi::class, ExperimentalNativeApi::class) +enum class TestPlatform { + Jvm, Native, Js, WasmJs, Wasi; +} + +expect val testPlatform: TestPlatform + class WireCodecTest { @Test @@ -343,8 +345,7 @@ class WireCodecTest { val buffer = Buffer() val decoder = WireDecoder(buffer) - decoder.readTag() - assertTrue(decoder.hadError()) + assertNull(decoder.readTag()) } @Test @@ -418,6 +419,11 @@ class WireCodecTest { @Test fun testReadAfterClose() { + // jvm has no close method + if (testPlatform == TestPlatform.Jvm) { + return + } + val fieldNr = 19 val buffer = Buffer() @@ -432,7 +438,7 @@ class WireCodecTest { try { val tag = decoder.readTag() assertNull(tag) - } catch (e: Exception) { + } catch (_: Exception) { // Expected exception in some implementations } } @@ -497,6 +503,11 @@ class WireCodecTest { @Test fun testBufferNotExhausted() { + // jvm reads buffer - buffered (ba dum tss) + if (testPlatform == TestPlatform.Jvm) { + return + } + val fieldNr = 1 val buffer = Buffer() diff --git a/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.js.kt b/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.js.kt new file mode 100644 index 000000000..c030ce564 --- /dev/null +++ b/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.js.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.internal + +internal actual fun WireDecoder.pushLimit(byteLen: Int): Int { + TODO("Not yet implemented") +} + +internal actual fun WireDecoder.popLimit(limit: Int) { + TODO("Not yet implemented") +} + +internal actual fun WireDecoder.bytesUntilLimit(): Int { + TODO("Not yet implemented") +} diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/WireDecoder.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/WireDecoder.jvm.kt index f56885157..f8d5def10 100644 --- a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/WireDecoder.jvm.kt +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/WireDecoder.jvm.kt @@ -4,9 +4,121 @@ package kotlinx.rpc.grpc.internal +import com.google.protobuf.CodedInputStream import kotlinx.io.Buffer -import kotlinx.io.Source +import kotlinx.io.asInputStream + +internal class WireDecoderJvm(source: Buffer) : WireDecoder { + // there is no way to omit coping here + internal val codedInputStream: CodedInputStream = CodedInputStream.newInstance(source.asInputStream()) + + // errors in jvm are exceptions + override fun hadError(): Boolean { + return false + } + + override fun readTag(): KTag? { + val tag = codedInputStream.readTag().toUInt() + if (tag == 0u) { + return null + } + + return KTag.fromOrNull(tag) + } + + override fun readBool(): Boolean { + return codedInputStream.readBool() + } + + override fun readInt32(): Int { + return codedInputStream.readInt32() + } + + override fun readInt64(): Long { + return codedInputStream.readInt64() + } + + override fun readUInt32(): UInt { + // todo check java unsigned types + return codedInputStream.readUInt32().toUInt() + } + + override fun readUInt64(): ULong { + // todo check java unsigned types + return codedInputStream.readUInt64().toULong() + } + + override fun readSInt32(): Int { + return codedInputStream.readSInt32() + } + + override fun readSInt64(): Long { + return codedInputStream.readSInt64() + } + + override fun readFixed32(): UInt { + // todo check java unsigned types + return codedInputStream.readFixed32().toUInt() + } + + override fun readFixed64(): ULong { + // todo check java unsigned types + return codedInputStream.readFixed64().toULong() + } + + override fun readSFixed32(): Int { + return codedInputStream.readSFixed32() + } + + override fun readSFixed64(): Long { + return codedInputStream.readSFixed64() + } + + override fun readFloat(): Float { + return codedInputStream.readFloat() + } + + override fun readDouble(): Double { + return codedInputStream.readDouble() + } + + override fun readEnum(): Int { + return codedInputStream.readEnum() + } + + override fun readString(): String { + return codedInputStream.readStringRequireUtf8() + } + + override fun readBytes(): ByteArray { + return codedInputStream.readByteArray() + } + + override fun readPackedBool() = readPackedInternal(this::readBool) + override fun readPackedInt32() = readPackedInternal(this::readInt32) + override fun readPackedInt64() = readPackedInternal(this::readInt64) + override fun readPackedUInt32() = readPackedInternal(this::readUInt32) + override fun readPackedUInt64() = readPackedInternal(this::readUInt64) + override fun readPackedSInt32() = readPackedInternal(this::readSInt32) + override fun readPackedSInt64() = readPackedInternal(this::readSInt64) + override fun readPackedEnum() = readPackedInternal(this::readEnum) + override fun readPackedFixed32(): List = readPackedInternal(::readFixed32) + override fun readPackedFixed64(): List = readPackedInternal(::readFixed64) + override fun readPackedSFixed32(): List = readPackedInternal(::readSFixed32) + override fun readPackedSFixed64(): List = readPackedInternal(::readSFixed64) + override fun readPackedFloat(): List = readPackedInternal(::readFloat) + override fun readPackedDouble(): List = readPackedInternal(::readDouble) + + override fun close() {} + + private fun readPackedInternal(read: () -> T) = readPackedVarInternal( + size = { -1 }, + readFn = read, + withError = { }, + hadError = { false }, + ) +} internal actual fun WireDecoder(source: Buffer): WireDecoder { - TODO("Not yet implemented") -} \ No newline at end of file + return WireDecoderJvm(source) +} diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/WireEncoder.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/WireEncoder.jvm.kt index 00c0b3246..e8542ac89 100644 --- a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/WireEncoder.jvm.kt +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/WireEncoder.jvm.kt @@ -4,8 +4,219 @@ package kotlinx.rpc.grpc.internal +import com.google.protobuf.ByteString +import com.google.protobuf.CodedOutputStream import kotlinx.io.Sink +import kotlinx.io.asOutputStream + +private class WireEncoderJvm(sink: Sink) : WireEncoder { + private val codedOutputStream = CodedOutputStream.newInstance(sink.asOutputStream()) + + override fun flush() { + codedOutputStream.flush() + } + + override fun writeBool(fieldNr: Int, value: Boolean): Boolean { + codedOutputStream.writeBool(fieldNr, value) + return true + } + + override fun writeInt32(fieldNr: Int, value: Int): Boolean { + codedOutputStream.writeInt32(fieldNr, value) + return true + } + + override fun writeInt64(fieldNr: Int, value: Long): Boolean { + codedOutputStream.writeInt64(fieldNr, value) + return true + } + + override fun writeUInt32(fieldNr: Int, value: UInt): Boolean { + // todo check java unsigned types + codedOutputStream.writeUInt32(fieldNr, value.toInt()) + return true + } + + override fun writeUInt64(fieldNr: Int, value: ULong): Boolean { + // todo check java unsigned types + codedOutputStream.writeUInt64(fieldNr, value.toLong()) + return true + } + + override fun writeSInt32(fieldNr: Int, value: Int): Boolean { + codedOutputStream.writeSInt32(fieldNr, value) + return true + } + + override fun writeSInt64(fieldNr: Int, value: Long): Boolean { + codedOutputStream.writeSInt64(fieldNr, value) + return true + } + + override fun writeFixed32(fieldNr: Int, value: UInt): Boolean { + // todo check java unsigned types + codedOutputStream.writeFixed32(fieldNr, value.toInt()) + return true + } + + override fun writeFixed64(fieldNr: Int, value: ULong): Boolean { + // todo check java unsigned types + codedOutputStream.writeFixed64(fieldNr, value.toLong()) + return true + } + + override fun writeSFixed32(fieldNr: Int, value: Int): Boolean { + codedOutputStream.writeSFixed32(fieldNr, value) + return true + } + + override fun writeSFixed64(fieldNr: Int, value: Long): Boolean { + codedOutputStream.writeSFixed64(fieldNr, value) + return true + } + + override fun writeFloat(fieldNr: Int, value: Float): Boolean { + codedOutputStream.writeFloat(fieldNr, value) + return true + } + + override fun writeDouble(fieldNr: Int, value: Double): Boolean { + codedOutputStream.writeDouble(fieldNr, value) + return true + } + + override fun writeEnum(fieldNr: Int, value: Int): Boolean { + codedOutputStream.writeEnum(fieldNr, value) + return true + } + + override fun writeBytes(fieldNr: Int, value: ByteArray): Boolean { + codedOutputStream.writeByteArray(fieldNr, value) + return true + } + + override fun writeString(fieldNr: Int, value: String): Boolean { + codedOutputStream.writeString(fieldNr, value) + return true + } + + override fun writePackedBool( + fieldNr: Int, + value: List, + fieldSize: Int, + ): Boolean { + writePackedInternal(fieldNr, value, fieldSize, CodedOutputStream::writeBoolNoTag) + return true + } + + override fun writePackedInt32( + fieldNr: Int, + value: List, + fieldSize: Int, + ): Boolean { + writePackedInternal(fieldNr, value, fieldSize, CodedOutputStream::writeInt32NoTag) + return true + } + + override fun writePackedInt64( + fieldNr: Int, + value: List, + fieldSize: Int, + ): Boolean { + writePackedInternal(fieldNr, value, fieldSize, CodedOutputStream::writeInt64NoTag) + return true + } + + override fun writePackedUInt32( + fieldNr: Int, + value: List, + fieldSize: Int, + ): Boolean { + writePackedInternal(fieldNr, value, fieldSize) { codedOutputStream, v -> + codedOutputStream.writeUInt32NoTag(v.toInt()) + } + return true + } + + override fun writePackedUInt64( + fieldNr: Int, + value: List, + fieldSize: Int, + ): Boolean { + writePackedInternal(fieldNr, value, fieldSize) { codedOutputStream, v -> + codedOutputStream.writeUInt64NoTag(v.toLong()) + } + return true + } + + override fun writePackedSInt32( + fieldNr: Int, + value: List, + fieldSize: Int, + ): Boolean { + writePackedInternal(fieldNr, value, fieldSize, CodedOutputStream::writeSInt32NoTag) + return true + } + + override fun writePackedSInt64( + fieldNr: Int, + value: List, + fieldSize: Int, + ): Boolean { + writePackedInternal(fieldNr, value, fieldSize, CodedOutputStream::writeSInt64NoTag) + return true + } + + override fun writePackedFixed32(fieldNr: Int, value: List): Boolean { + writePackedInternal(fieldNr, value, value.size * UInt.SIZE_BYTES) { codedOutputStream, v -> + codedOutputStream.writeFixed32NoTag(v.toInt()) + } + return true + } + + override fun writePackedFixed64(fieldNr: Int, value: List): Boolean { + writePackedInternal(fieldNr, value, value.size * ULong.SIZE_BYTES) { codedOutputStream, v -> + codedOutputStream.writeFixed64NoTag(v.toLong()) + } + return true + } + + override fun writePackedSFixed32(fieldNr: Int, value: List): Boolean { + writePackedInternal(fieldNr, value, value.size * Int.SIZE_BYTES, CodedOutputStream::writeSFixed32NoTag) + return true + } + + override fun writePackedSFixed64(fieldNr: Int, value: List): Boolean { + writePackedInternal(fieldNr, value, value.size * Long.SIZE_BYTES, CodedOutputStream::writeSFixed64NoTag) + return true + } + + override fun writePackedFloat(fieldNr: Int, value: List): Boolean { + writePackedInternal(fieldNr, value, value.size * Float.SIZE_BYTES, CodedOutputStream::writeFloatNoTag) + return true + } + + override fun writePackedDouble(fieldNr: Int, value: List): Boolean { + writePackedInternal(fieldNr, value, value.size * Double.SIZE_BYTES, CodedOutputStream::writeDoubleNoTag) + return true + } + + private inline fun writePackedInternal( + fieldNr: Int, + value: List, + fieldSize: Int, + crossinline writer: (CodedOutputStream, T) -> Unit, + ): Boolean { + codedOutputStream.writeTag(fieldNr, WireType.LENGTH_DELIMITED.ordinal) + // write the field size of the packed field + codedOutputStream.writeInt32NoTag(fieldSize) + for (v in value) { + writer(codedOutputStream, v) + } + return true + } +} internal actual fun WireEncoder(sink: Sink): WireEncoder { - TODO("Not yet implemented") -} \ No newline at end of file + return WireEncoderJvm(sink) +} diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/WireSize.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/WireSize.jvm.kt index e70b9bd00..3697c3d91 100644 --- a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/WireSize.jvm.kt +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/WireSize.jvm.kt @@ -4,26 +4,35 @@ package kotlinx.rpc.grpc.internal +import com.google.protobuf.CodedOutputStream.computeInt32SizeNoTag +import com.google.protobuf.CodedOutputStream.computeInt64SizeNoTag +import com.google.protobuf.CodedOutputStream.computeUInt32SizeNoTag +import com.google.protobuf.CodedOutputStream.computeUInt64SizeNoTag +import com.google.protobuf.CodedOutputStream.computeSInt32SizeNoTag +import com.google.protobuf.CodedOutputStream.computeSInt64SizeNoTag + internal actual fun WireSize.int32(value: Int): UInt { - TODO("Not yet implemented") + return computeInt32SizeNoTag(value).toUInt() } internal actual fun WireSize.int64(value: Long): UInt { - TODO("Not yet implemented") + return computeInt64SizeNoTag(value).toUInt() } internal actual fun WireSize.uInt32(value: UInt): UInt { - TODO("Not yet implemented") + // todo check java unsigned types + return computeUInt32SizeNoTag(value.toInt()).toUInt() } internal actual fun WireSize.uInt64(value: ULong): UInt { - TODO("Not yet implemented") + // todo check java unsigned types + return computeUInt64SizeNoTag(value.toLong()).toUInt() } internal actual fun WireSize.sInt32(value: Int): UInt { - TODO("Not yet implemented") + return computeSInt32SizeNoTag(value).toUInt() } internal actual fun WireSize.sInt64(value: Long): UInt { - TODO("Not yet implemented") -} \ No newline at end of file + return computeSInt64SizeNoTag(value).toUInt() +} diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.jvm.kt new file mode 100644 index 000000000..378339170 --- /dev/null +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.jvm.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.internal + +internal actual fun WireDecoder.pushLimit(byteLen: Int): Int { + return (this as WireDecoderJvm).codedInputStream.pushLimit(byteLen) +} + +internal actual fun WireDecoder.popLimit(limit: Int) { + (this as WireDecoderJvm).codedInputStream.popLimit(limit) +} + +internal actual fun WireDecoder.bytesUntilLimit(): Int { + return (this as WireDecoderJvm).codedInputStream.bytesUntilLimit +} diff --git a/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/TestReferenceService.kt b/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/TestReferenceService.kt index 2b6be5b8e..3eb7ff64a 100644 --- a/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/TestReferenceService.kt +++ b/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/core/test/TestReferenceService.kt @@ -120,6 +120,8 @@ class TestReferenceService : GrpcServerTest() { fun testRepeated() = runGrpcTest { grpcClient -> val service = grpcClient.withService() val result = service.Repeated(Repeated { + listFixed32 = listOf(0u, 1u, 2u) + listInt32 = listOf(0, 1, 2) listString = listOf("test", "hello") listReference = listOf(kotlinx.rpc.grpc.test.References { other = kotlinx.rpc.grpc.test.Other { @@ -129,12 +131,16 @@ class TestReferenceService : GrpcServerTest() { }) assertEquals(listOf("test", "hello"), result.listString) + assertEquals(listOf(0u, 1u, 2u), result.listFixed32) + assertEquals(listOf(0, 1, 2), result.listInt32) assertEquals(1, result.listReference.size) assertEquals(42, result.listReference[0].other.field) val resultEmpty = service.Repeated(Repeated {}) assertEquals(emptyList(), resultEmpty.listString) + assertEquals(emptyList(), resultEmpty.listFixed32) + assertEquals(emptyList(), resultEmpty.listInt32) assertEquals(emptyList(), resultEmpty.listReference) } diff --git a/grpc/grpc-core/src/jvmTest/proto/repeated.proto b/grpc/grpc-core/src/jvmTest/proto/repeated.proto index 4d61a1840..c7256377a 100644 --- a/grpc/grpc-core/src/jvmTest/proto/repeated.proto +++ b/grpc/grpc-core/src/jvmTest/proto/repeated.proto @@ -5,6 +5,8 @@ package kotlinx.rpc.grpc.test; import 'reference_package.proto'; message Repeated { - repeated string listString = 1; - repeated References listReference = 2; + repeated int32 listInt32 = 1; + repeated fixed32 listFixed32 = 2; + repeated string listString = 3; + repeated References listReference = 4; } diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/WireDecoder.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/WireDecoder.native.kt index 782305b1f..dac16a33c 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/WireDecoder.native.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/WireDecoder.native.kt @@ -12,10 +12,6 @@ import kotlin.experimental.ExperimentalNativeApi import kotlin.math.min import kotlin.native.ref.createCleaner -// TODO: Evaluate if this buffer size is suitable for all targets (KRPC-186) -// maximum buffer size to allocate as contiguous memory in bytes -private const val MAX_PACKED_BULK_SIZE: Int = 1_000_000 - @OptIn(ExperimentalForeignApi::class, ExperimentalNativeApi::class) internal class WireDecoderNative(private val source: Buffer) : WireDecoder { @@ -241,28 +237,12 @@ internal class WireDecoderNative(private val source: Buffer) : WireDecoder { DoubleArray::asList, ) - private inline fun readPackedVarInternal( - crossinline readFn: () -> T - ): List { - val byteLen = readInt32() - if (hadError) return emptyList() - if (byteLen < 0) return emptyList().withError() - if (source.size < byteLen) return emptyList().withError() - if (byteLen == 0) return emptyList() // actually an empty list (no error) - - val limit = pw_decoder_push_limit(raw, byteLen) - - val result = mutableListOf() - - while (pw_decoder_bytes_until_limit(raw) > 0) { - val elem = readFn() - if (hadError) break - result.add(elem) - } - - pw_decoder_pop_limit(raw, limit) - return result - } + private fun readPackedVarInternal(read: () -> T) = readPackedVarInternal( + size = { source.size }, + readFn = read, + withError = { hadError = true }, + hadError = { hadError }, + ) /* * Based on the length of the packed repeated field, one of two list strategies is chosen. @@ -334,4 +314,4 @@ internal class WireDecoderNative(private val source: Buffer) : WireDecoder { } } -internal actual fun WireDecoder(source: Buffer): WireDecoder = WireDecoderNative(source) \ No newline at end of file +internal actual fun WireDecoder(source: Buffer): WireDecoder = WireDecoderNative(source) diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/WireEncoder.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/WireEncoder.native.kt index 9944ce0af..9fd4b22a2 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/WireEncoder.native.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/WireEncoder.native.kt @@ -50,8 +50,8 @@ internal class WireEncoderNative(private val sink: Sink) : WireEncoder { pw_encoder_flush(raw) } - override fun writeBool(field: Int, value: Boolean): Boolean { - return pw_encoder_write_bool(raw, field, value) + override fun writeBool(fieldNr: Int, value: Boolean): Boolean { + return pw_encoder_write_bool(raw, fieldNr, value) } override fun writeInt32(fieldNr: Int, value: Int): Boolean { @@ -184,4 +184,4 @@ private inline fun WireEncoderNative.writePackedInternal( } } return true -} \ No newline at end of file +} diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.native.kt new file mode 100644 index 000000000..21a58f0f5 --- /dev/null +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.native.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +@file:OptIn(ExperimentalForeignApi::class) + +package kotlinx.rpc.grpc.internal + +import kotlinx.cinterop.ExperimentalForeignApi +import libprotowire.pw_decoder_bytes_until_limit +import libprotowire.pw_decoder_pop_limit +import libprotowire.pw_decoder_push_limit + +internal actual fun WireDecoder.pushLimit(byteLen: Int): Int { + return pw_decoder_push_limit((this as WireDecoderNative).raw, byteLen) +} + +internal actual fun WireDecoder.popLimit(limit: Int) { + pw_decoder_pop_limit((this as WireDecoderNative).raw, limit) +} + +internal actual fun WireDecoder.bytesUntilLimit(): Int { + return pw_decoder_bytes_until_limit((this as WireDecoderNative).raw) +} diff --git a/grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/internal/WireCodecTest.native.kt b/grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/internal/WireCodecTest.native.kt new file mode 100644 index 000000000..a90765af4 --- /dev/null +++ b/grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/internal/WireCodecTest.native.kt @@ -0,0 +1,7 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.internal + +actual val testPlatform: TestPlatform = TestPlatform.Native diff --git a/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.wasmJs.kt b/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.wasmJs.kt new file mode 100644 index 000000000..c030ce564 --- /dev/null +++ b/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.wasmJs.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.internal + +internal actual fun WireDecoder.pushLimit(byteLen: Int): Int { + TODO("Not yet implemented") +} + +internal actual fun WireDecoder.popLimit(limit: Int) { + TODO("Not yet implemented") +} + +internal actual fun WireDecoder.bytesUntilLimit(): Int { + TODO("Not yet implemented") +} diff --git a/grpc/grpc-ktor-server/gradle.properties b/grpc/grpc-ktor-server/gradle.properties index 1c532ca1e..b73a3ad89 100644 --- a/grpc/grpc-ktor-server/gradle.properties +++ b/grpc/grpc-ktor-server/gradle.properties @@ -3,6 +3,8 @@ # kotlinx.rpc.exclude.wasmWasi=true +kotlinx.rpc.exclude.js=true +kotlinx.rpc.exclude.wasmJs=true kotlinx.rpc.exclude.iosArm64=true kotlinx.rpc.exclude.iosX64=true kotlinx.rpc.exclude.iosSimulatorArm64=true diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinGenerator.kt b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinGenerator.kt index 1e6d73ee2..ad392f394 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinGenerator.kt +++ b/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinGenerator.kt @@ -309,28 +309,12 @@ class ModelToKotlinGenerator( val fq by value importRootDeclarationIfNeeded(fq, "toPlatform", true) } - is FieldType.List -> { - when (value) { - is FieldType.Reference -> ".map { it.toPlatform() }".also { - val fq by value.value - importRootDeclarationIfNeeded(fq, "toPlatform", true) - } - is FieldType.IntegralType -> "" - else -> error("Unsupported type: $value") - } - } + is FieldType.List -> ".map { it${value.toPlatformCast()} }" is FieldType.Map -> { val entry by entry - when (val value = entry.value) { - is FieldType.Reference -> ".mapValues { it.value.toPlatform() }".also { - val fq by value.value - importRootDeclarationIfNeeded(fq, "toPlatform", true) - } - is FieldType.IntegralType -> "" - else -> error("Unsupported type: $value") - } + ".mapValues { it.value${entry.value.toPlatformCast()} }" } else -> "" @@ -348,28 +332,12 @@ class ModelToKotlinGenerator( val fq by value importRootDeclarationIfNeeded(fq, "toKotlin", true) } - is FieldType.List -> { - when (value) { - is FieldType.Reference -> ".map { it.toKotlin() }".also { - val fq by value.value - importRootDeclarationIfNeeded(fq, "toKotlin", true) - } - is FieldType.IntegralType -> ".toList()" - else -> error("Unsupported type: $value") - } - } + is FieldType.List -> ".map { it${value.toKotlinCast()} }" is FieldType.Map -> { val entry by entry - when (val value = entry.value) { - is FieldType.Reference -> ".mapValues { it.value.toKotlin() }".also { - val fq by value.value - importRootDeclarationIfNeeded(fq, "toKotlin", true) - } - is FieldType.IntegralType -> "" - else -> error("Unsupported type: $value") - } + ".mapValues { it.value${entry.value.toKotlinCast()} }" } else -> ""