Skip to content

Commit 65f49d6

Browse files
committed
Added jvm codecs implementations (#414)
1 parent 3dc2b8a commit 65f49d6

File tree

20 files changed

+537
-104
lines changed

20 files changed

+537
-104
lines changed

grpc/grpc-core/build.gradle.kts

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ kotlin {
3030

3131
implementation(libs.atomicfu)
3232
implementation(libs.kotlinx.io.core)
33+
implementation(libs.kotlinx.collections.immutable)
34+
}
35+
}
36+
37+
commonTest {
38+
dependencies {
39+
implementation(libs.kotlin.test)
3340
}
3441
}
3542

@@ -39,6 +46,7 @@ kotlin {
3946
api(libs.grpc.util)
4047
api(libs.grpc.stub)
4148
api(libs.grpc.protobuf)
49+
api("io.grpc:grpc-protobuf-lite:${libs.versions.grpc.asProvider().get()}")
4250
implementation(libs.grpc.kotlin.stub) // causes problems to jpms if api
4351
api(libs.protobuf.java.util)
4452
implementation(libs.protobuf.kotlin)
@@ -50,7 +58,6 @@ kotlin {
5058
implementation(projects.grpc.grpcCore)
5159
implementation(libs.coroutines.core)
5260
implementation(libs.coroutines.test)
53-
implementation(libs.kotlin.test)
5461

5562
implementation(libs.grpc.stub)
5663
implementation(libs.grpc.netty)
@@ -60,18 +67,6 @@ kotlin {
6067
implementation(libs.protobuf.kotlin)
6168
}
6269
}
63-
64-
nativeMain {
65-
dependencies {
66-
implementation(libs.kotlinx.collections.immutable)
67-
}
68-
}
69-
70-
nativeTest {
71-
dependencies {
72-
implementation(kotlin("test"))
73-
}
74-
}
7570
}
7671

7772
val grpcppCLib = projectDir.resolve("../grpcpp-c")

grpc/grpc-core/gradle.properties

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
# Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
33
#
44
kotlinx.rpc.exclude.wasmWasi=true
5+
kotlinx.rpc.exclude.js=true
6+
kotlinx.rpc.exclude.wasmJs=true
57
kotlinx.rpc.exclude.iosArm64=true
68
kotlinx.rpc.exclude.iosX64=true
79
kotlinx.rpc.exclude.iosSimulatorArm64=true
@@ -16,4 +18,4 @@ kotlinx.rpc.exclude.watchosArm32=true
1618
kotlinx.rpc.exclude.watchosArm64=true
1719
kotlinx.rpc.exclude.watchosDeviceArm64=true
1820
kotlinx.rpc.exclude.watchosSimulatorArm64=true
19-
kotlinx.rpc.exclude.watchosX64=true
21+
kotlinx.rpc.exclude.watchosX64=true

grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/WireDecoder.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ package kotlinx.rpc.grpc.internal
66

77
import kotlinx.io.Buffer
88

9+
// TODO: Evaluate if this buffer size is suitable for all targets (KRPC-186)
10+
// maximum buffer size to allocate as contiguous memory in bytes
11+
internal const val MAX_PACKED_BULK_SIZE: Int = 1_000_000
12+
913
/**
1014
* A platform-specific decoder for wire format data.
1115
*
@@ -78,4 +82,4 @@ internal interface WireDecoder : AutoCloseable {
7882
*
7983
* @param source The buffer containing the encoded wire-format data.
8084
*/
81-
internal expect fun WireDecoder(source: Buffer): WireDecoder
85+
internal expect fun WireDecoder(source: Buffer): WireDecoder

grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/WireEncoder.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import kotlinx.io.Sink
1717
@OptIn(ExperimentalUnsignedTypes::class)
1818
internal interface WireEncoder {
1919
fun flush()
20-
fun writeBool(field: Int, value: Boolean): Boolean
20+
fun writeBool(fieldNr: Int, value: Boolean): Boolean
2121
fun writeInt32(fieldNr: Int, value: Int): Boolean
2222
fun writeInt64(fieldNr: Int, value: Long): Boolean
2323
fun writeUInt32(fieldNr: Int, value: UInt): Boolean
@@ -51,4 +51,4 @@ internal interface WireEncoder {
5151
}
5252

5353

54-
internal expect fun WireEncoder(sink: Sink): WireEncoder
54+
internal expect fun WireEncoder(sink: Sink): WireEncoder
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.rpc.grpc.internal
6+
7+
import kotlinx.io.Buffer
8+
9+
internal expect fun WireDecoder.pushLimit(byteLen: Int): Int
10+
internal expect fun WireDecoder.popLimit(limit: Int)
11+
internal expect fun WireDecoder.bytesUntilLimit(): Int
12+
13+
internal inline fun <T : Any> WireDecoder.readPackedVarInternal(
14+
crossinline size: () -> Long,
15+
crossinline readFn: () -> T,
16+
crossinline withError: () -> Unit,
17+
crossinline hadError: () -> Boolean,
18+
): List<T> {
19+
val byteLen = readInt32()
20+
if (hadError()) {
21+
return emptyList()
22+
}
23+
if (byteLen < 0) {
24+
return emptyList<T>().apply { withError() }
25+
}
26+
val size = size()
27+
// no size check on jvm
28+
if (size != -1L && size < byteLen) {
29+
return emptyList<T>().apply { withError() }
30+
}
31+
if (byteLen == 0) {
32+
return emptyList() // actually an empty list (no error)
33+
}
34+
35+
val limit = pushLimit(byteLen)
36+
37+
val result = mutableListOf<T>()
38+
39+
while (bytesUntilLimit() > 0) {
40+
val elem = readFn()
41+
if (hadError()) {
42+
break
43+
}
44+
result.add(elem)
45+
}
46+
47+
popLimit(limit)
48+
return result
49+
}

grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/internal/WireCodecTest.kt renamed to grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/internal/WireCodecTest.kt

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44

55
package kotlinx.rpc.grpc.internal
66

7-
import kotlinx.cinterop.ExperimentalForeignApi
87
import kotlinx.io.Buffer
9-
import kotlin.experimental.ExperimentalNativeApi
108
import kotlin.test.*
119

12-
// TODO: Move this to the commonTest
13-
@OptIn(ExperimentalForeignApi::class, ExperimentalNativeApi::class)
10+
enum class TestPlatform {
11+
Jvm, Native, Js, WasmJs, Wasi;
12+
}
13+
14+
expect val testPlatform: TestPlatform
15+
1416
class WireCodecTest {
1517

1618
@Test
@@ -343,8 +345,7 @@ class WireCodecTest {
343345
val buffer = Buffer()
344346

345347
val decoder = WireDecoder(buffer)
346-
decoder.readTag()
347-
assertTrue(decoder.hadError())
348+
assertNull(decoder.readTag())
348349
}
349350

350351
@Test
@@ -418,6 +419,11 @@ class WireCodecTest {
418419

419420
@Test
420421
fun testReadAfterClose() {
422+
// jvm has no close method
423+
if (testPlatform == TestPlatform.Jvm) {
424+
return
425+
}
426+
421427
val fieldNr = 19
422428
val buffer = Buffer()
423429

@@ -432,7 +438,7 @@ class WireCodecTest {
432438
try {
433439
val tag = decoder.readTag()
434440
assertNull(tag)
435-
} catch (e: Exception) {
441+
} catch (_: Exception) {
436442
// Expected exception in some implementations
437443
}
438444
}
@@ -497,6 +503,11 @@ class WireCodecTest {
497503

498504
@Test
499505
fun testBufferNotExhausted() {
506+
// jvm reads buffer - buffered (ba dum tss)
507+
if (testPlatform == TestPlatform.Jvm) {
508+
return
509+
}
510+
500511
val fieldNr = 1
501512
val buffer = Buffer()
502513

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.rpc.grpc.internal
6+
7+
internal actual fun WireDecoder.pushLimit(byteLen: Int): Int {
8+
TODO("Not yet implemented")
9+
}
10+
11+
internal actual fun WireDecoder.popLimit(limit: Int) {
12+
TODO("Not yet implemented")
13+
}
14+
15+
internal actual fun WireDecoder.bytesUntilLimit(): Int {
16+
TODO("Not yet implemented")
17+
}

grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/WireDecoder.jvm.kt

Lines changed: 115 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,121 @@
44

55
package kotlinx.rpc.grpc.internal
66

7+
import com.google.protobuf.CodedInputStream
78
import kotlinx.io.Buffer
8-
import kotlinx.io.Source
9+
import kotlinx.io.asInputStream
10+
11+
internal class WireDecoderJvm(source: Buffer) : WireDecoder {
12+
// there is no way to omit coping here
13+
internal val codedInputStream: CodedInputStream = CodedInputStream.newInstance(source.asInputStream())
14+
15+
// errors in jvm are exceptions
16+
override fun hadError(): Boolean {
17+
return false
18+
}
19+
20+
override fun readTag(): KTag? {
21+
val tag = codedInputStream.readTag().toUInt()
22+
if (tag == 0u) {
23+
return null
24+
}
25+
26+
return KTag.fromOrNull(tag)
27+
}
28+
29+
override fun readBool(): Boolean {
30+
return codedInputStream.readBool()
31+
}
32+
33+
override fun readInt32(): Int {
34+
return codedInputStream.readInt32()
35+
}
36+
37+
override fun readInt64(): Long {
38+
return codedInputStream.readInt64()
39+
}
40+
41+
override fun readUInt32(): UInt {
42+
// todo check java unsigned types
43+
return codedInputStream.readUInt32().toUInt()
44+
}
45+
46+
override fun readUInt64(): ULong {
47+
// todo check java unsigned types
48+
return codedInputStream.readUInt64().toULong()
49+
}
50+
51+
override fun readSInt32(): Int {
52+
return codedInputStream.readSInt32()
53+
}
54+
55+
override fun readSInt64(): Long {
56+
return codedInputStream.readSInt64()
57+
}
58+
59+
override fun readFixed32(): UInt {
60+
// todo check java unsigned types
61+
return codedInputStream.readFixed32().toUInt()
62+
}
63+
64+
override fun readFixed64(): ULong {
65+
// todo check java unsigned types
66+
return codedInputStream.readFixed64().toULong()
67+
}
68+
69+
override fun readSFixed32(): Int {
70+
return codedInputStream.readSFixed32()
71+
}
72+
73+
override fun readSFixed64(): Long {
74+
return codedInputStream.readSFixed64()
75+
}
76+
77+
override fun readFloat(): Float {
78+
return codedInputStream.readFloat()
79+
}
80+
81+
override fun readDouble(): Double {
82+
return codedInputStream.readDouble()
83+
}
84+
85+
override fun readEnum(): Int {
86+
return codedInputStream.readEnum()
87+
}
88+
89+
override fun readString(): String {
90+
return codedInputStream.readStringRequireUtf8()
91+
}
92+
93+
override fun readBytes(): ByteArray {
94+
return codedInputStream.readByteArray()
95+
}
96+
97+
override fun readPackedBool() = readPackedInternal(this::readBool)
98+
override fun readPackedInt32() = readPackedInternal(this::readInt32)
99+
override fun readPackedInt64() = readPackedInternal(this::readInt64)
100+
override fun readPackedUInt32() = readPackedInternal(this::readUInt32)
101+
override fun readPackedUInt64() = readPackedInternal(this::readUInt64)
102+
override fun readPackedSInt32() = readPackedInternal(this::readSInt32)
103+
override fun readPackedSInt64() = readPackedInternal(this::readSInt64)
104+
override fun readPackedEnum() = readPackedInternal(this::readEnum)
105+
override fun readPackedFixed32(): List<UInt> = readPackedInternal(::readFixed32)
106+
override fun readPackedFixed64(): List<ULong> = readPackedInternal(::readFixed64)
107+
override fun readPackedSFixed32(): List<Int> = readPackedInternal(::readSFixed32)
108+
override fun readPackedSFixed64(): List<Long> = readPackedInternal(::readSFixed64)
109+
override fun readPackedFloat(): List<Float> = readPackedInternal(::readFloat)
110+
override fun readPackedDouble(): List<Double> = readPackedInternal(::readDouble)
111+
112+
override fun close() {}
113+
114+
private fun <T : Any> readPackedInternal(read: () -> T) = readPackedVarInternal(
115+
size = { -1 },
116+
readFn = read,
117+
withError = { },
118+
hadError = { false },
119+
)
120+
}
9121

10122
internal actual fun WireDecoder(source: Buffer): WireDecoder {
11-
TODO("Not yet implemented")
12-
}
123+
return WireDecoderJvm(source)
124+
}

0 commit comments

Comments
 (0)