Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion grpc/grpc-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ plugins {
alias(libs.plugins.kotlinx.rpc)
}



kotlin {
compilerOptions {
freeCompilerArgs.add("-Xexpect-actual-classes")
Expand All @@ -27,6 +29,7 @@ kotlin {
api(libs.coroutines.core)

implementation(libs.atomicfu)
implementation(libs.kotlinx.io.core)
}
}

Expand Down Expand Up @@ -58,6 +61,12 @@ kotlin {
}
}

nativeMain {
dependencies {
implementation(libs.kotlinx.collections.immutable)
}
}

nativeTest {
dependencies {
implementation(kotlin("test"))
Expand All @@ -83,7 +92,7 @@ kotlin {
val buildGrpcppCLib = tasks.register<Exec>("buildGrpcppCLib") {
group = "build"
workingDir = grpcppCLib
commandLine("bash", "-c", "bazel build :grpcpp_c_static --config=release")
commandLine("bash", "-c", "bazel build :grpcpp_c_static :protowire_static --config=release")
inputs.files(fileTree(grpcppCLib) { exclude("bazel-*/**") })
outputs.dir(grpcppCLib.resolve("bazel-bin"))

Expand All @@ -108,6 +117,22 @@ kotlin {
tasks.named(interopTask, CInteropProcess::class) {
dependsOn(buildGrpcppCLib)
}


val libprotowire by creating {
includeDirs(
grpcppCLib.resolve("include")
)
extraOpts(
"-libraryPath", "${grpcppCLib.resolve("bazel-out/darwin_arm64-opt/bin")}",
)
}

val libUpbTask = "cinterop${libprotowire.name.capitalized()}${it.targetName.capitalized()}"
tasks.named(libUpbTask, CInteropProcess::class) {
dependsOn(buildGrpcppCLib)
}

}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* 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.rpc.grpc.internal.KTag.Companion.K_TAG_TYPE_BITS

internal enum class WireType {
VARINT, // 0
FIXED64, // 1
LENGTH_DELIMITED, // 2
START_GROUP, // 3
END_GROUP, // 4
FIXED32, // 5
}

internal data class KTag(val fieldNr: Int, val wireType: WireType) {

init {
check(isValidFieldNr(fieldNr)) { "Invalid field number: $fieldNr" }
}

companion object {
// Number of bits in a tag which identify the wire type.
const val K_TAG_TYPE_BITS: Int = 3;

// Mask for those bits. (just 0b111)
val K_TAG_TYPE_MASK: UInt = (1u shl K_TAG_TYPE_BITS) - 1u
}
}

internal fun KTag.toRawKTag(): UInt {
return (fieldNr.toUInt() shl K_TAG_TYPE_BITS) or wireType.ordinal.toUInt()
}

internal fun KTag.Companion.fromOrNull(rawKTag: UInt): KTag? {
val type = (rawKTag and K_TAG_TYPE_MASK).toInt()
val field = (rawKTag shr K_TAG_TYPE_BITS).toInt()
if (!isValidFieldNr(field)) {
return null
}
if (type >= WireType.entries.size) {
return null
}
return KTag(field, WireType.entries[type])
}

internal fun KTag.Companion.isValidFieldNr(fieldNr: Int): Boolean {
return 1 <= fieldNr && fieldNr <= 536_870_911
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* 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

/**
* A platform-specific decoder for wire format data.
*
* This decoder is used by first calling [readTag], than looking up the field based on the field number in the returned,
* tag and then calling the actual `read*()` method to read the value to the corresponding field.
*
* [hadError] indicates an error during decoding. While calling `read*()` is safe, the returned values
* are meaningless if [hadError] returns `true`.
*
* NOTE: If the [hadError] after a call to `read*()` returns `false`, it doesn't mean that the
* value is correctly decoded. E.g., the following test will pass:
* ```kt
* val fieldNr = 1
* val buffer = Buffer()
*
* val encoder = WireEncoder(buffer)
* assertTrue(encoder.writeInt32(fieldNr, 12312))
* encoder.flush()
*
* WireDecoder(buffer).use { decoder ->
* decoder.readTag()
* decoder.readBool()
* assertFalse(decoder.hasError())
* }
* ```
*/
internal interface WireDecoder : AutoCloseable {
fun hadError(): Boolean
fun readTag(): KTag?
fun readBool(): Boolean
fun readInt32(): Int
fun readInt64(): Long
fun readUInt32(): UInt
fun readUInt64(): ULong
fun readSInt32(): Int
fun readSInt64(): Long
fun readFixed32(): UInt
fun readFixed64(): ULong
fun readSFixed32(): Int
fun readSFixed64(): Long
fun readFloat(): Float
fun readDouble(): Double

fun readEnum(): Int
fun readString(): String
fun readBytes(): ByteArray
fun readPackedBool(): List<Boolean>
fun readPackedInt32(): List<Int>
fun readPackedInt64(): List<Long>
fun readPackedSInt32(): List<Int>
fun readPackedSInt64(): List<Long>
fun readPackedUInt32(): List<UInt>
fun readPackedUInt64(): List<ULong>
fun readPackedFixed32(): List<UInt>
fun readPackedFixed64(): List<ULong>
fun readPackedSFixed32(): List<Int>
fun readPackedSFixed64(): List<Long>
fun readPackedFloat(): List<Float>
fun readPackedDouble(): List<Double>
fun readPackedEnum(): List<Int>
}

/**
* Creates a platform-specific [WireDecoder].
*
* This constructor takes a [Buffer] instead of a [kotlinx.io.Source] because
* the native implementation (`WireDecoderNative`) depends on [Buffer]'s internal structure.
*
* NOTE: Do not use the [source] buffer while the [WireDecoder] is still open.
*
* @param source The buffer containing the encoded wire-format data.
*/
internal expect fun WireDecoder(source: Buffer): WireDecoder
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* 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.Sink

/**
* A platform-specific class that encodes values into protobuf's wire format.
*
* If one `write*()` method returns false, the encoding of the value failed
* and no further encodings can be performed on this [WireEncoder].
*
* [flush] must be called to ensure that all data is written to the [Sink].
*/
@OptIn(ExperimentalUnsignedTypes::class)
internal interface WireEncoder {
fun flush()
fun writeBool(field: 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
fun writeUInt64(fieldNr: Int, value: ULong): Boolean
fun writeSInt32(fieldNr: Int, value: Int): Boolean
fun writeSInt64(fieldNr: Int, value: Long): Boolean
fun writeFixed32(fieldNr: Int, value: UInt): Boolean
fun writeFixed64(fieldNr: Int, value: ULong): Boolean
fun writeSFixed32(fieldNr: Int, value: Int): Boolean
fun writeSFixed64(fieldNr: Int, value: Long): Boolean
fun writeFloat(fieldNr: Int, value: Float): Boolean
fun writeDouble(fieldNr: Int, value: Double): Boolean
fun writeEnum(fieldNr: Int, value: Int): Boolean
fun writeBytes(fieldNr: Int, value: ByteArray): Boolean
fun writeString(fieldNr: Int, value: String): Boolean
fun writePackedBool(fieldNr: Int, value: List<Boolean>, fieldSize: Int): Boolean
fun writePackedInt32(fieldNr: Int, value: List<Int>, fieldSize: Int): Boolean
fun writePackedInt64(fieldNr: Int, value: List<Long>, fieldSize: Int): Boolean
fun writePackedUInt32(fieldNr: Int, value: List<UInt>, fieldSize: Int): Boolean
fun writePackedUInt64(fieldNr: Int, value: List<ULong>, fieldSize: Int): Boolean
fun writePackedSInt32(fieldNr: Int, value: List<Int>, fieldSize: Int): Boolean
fun writePackedSInt64(fieldNr: Int, value: List<Long>, fieldSize: Int): Boolean
fun writePackedFixed32(fieldNr: Int, value: List<UInt>): Boolean
fun writePackedFixed64(fieldNr: Int, value: List<ULong>): Boolean
fun writePackedSFixed32(fieldNr: Int, value: List<Int>): Boolean
fun writePackedSFixed64(fieldNr: Int, value: List<Long>): Boolean
fun writePackedFloat(fieldNr: Int, value: List<Float>): Boolean
fun writePackedDouble(fieldNr: Int, value: List<Double>): Boolean
fun writePackedEnum(fieldNr: Int, value: List<Int>, fieldSize: Int) =
writePackedInt32(fieldNr, value, fieldSize)
}


internal expect fun WireEncoder(sink: Sink): WireEncoder
Original file line number Diff line number Diff line change
@@ -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.
*/

package kotlinx.rpc.grpc.internal

internal object WireSize

internal expect fun WireSize.int32(value: Int): UInt
internal expect fun WireSize.int64(value: Long): UInt
internal expect fun WireSize.uInt32(value: UInt): UInt
internal expect fun WireSize.uInt64(value: ULong): UInt
internal expect fun WireSize.sInt32(value: Int): UInt
internal expect fun WireSize.sInt64(value: Long): UInt

internal fun WireSize.bool(value: Boolean) = int32(if (value) 1 else 0)
internal fun WireSize.enum(value: Int) = int32(value)
internal fun WireSize.packedInt32(value: List<Int>) = value.sumOf { int32(it) }
internal fun WireSize.packedInt64(value: List<Long>) = value.sumOf { int64(it) }
internal fun WireSize.packedUInt32(value: List<UInt>) = value.sumOf { uInt32(it) }
internal fun WireSize.packedUInt64(value: List<ULong>) = value.sumOf { uInt64(it) }
internal fun WireSize.packedSInt32(value: List<Int>) = value.sumOf { sInt32(it) }
internal fun WireSize.packedSInt64(value: List<Long>) = value.sumOf { sInt64(it) }
internal fun WireSize.packedEnum(value: List<Int>) = value.sumOf { enum(it) }
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* 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
import kotlinx.io.Source

internal actual fun WireDecoder(source: Buffer): WireDecoder {
TODO("Not yet implemented")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* 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.Sink

internal actual fun WireEncoder(sink: Sink): WireEncoder {
TODO("Not yet implemented")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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 WireSize.int32(value: Int): UInt {
TODO("Not yet implemented")
}

internal actual fun WireSize.int64(value: Long): UInt {
TODO("Not yet implemented")
}

internal actual fun WireSize.uInt32(value: UInt): UInt {
TODO("Not yet implemented")
}

internal actual fun WireSize.uInt64(value: ULong): UInt {
TODO("Not yet implemented")
}

internal actual fun WireSize.sInt32(value: Int): UInt {
TODO("Not yet implemented")
}

internal actual fun WireSize.sInt64(value: Long): UInt {
TODO("Not yet implemented")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* 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
import kotlinx.io.Source

internal actual fun WireDecoder(source: Buffer): WireDecoder {
TODO("Not yet implemented")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* 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.Sink

internal actual fun WireEncoder(sink: Sink): WireEncoder {
TODO("Not yet implemented")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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 WireSize.int32(value: Int): UInt {
TODO("Not yet implemented")
}

internal actual fun WireSize.int64(value: Long): UInt {
TODO("Not yet implemented")
}

internal actual fun WireSize.uInt32(value: UInt): UInt {
TODO("Not yet implemented")
}

internal actual fun WireSize.uInt64(value: ULong): UInt {
TODO("Not yet implemented")
}

internal actual fun WireSize.sInt32(value: Int): UInt {
TODO("Not yet implemented")
}

internal actual fun WireSize.sInt64(value: Long): UInt {
TODO("Not yet implemented")
}
Loading
Loading