commonMain.kotlinx.serialization.protobuf.internal.Helpers.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kotlinx-serialization-protobuf
Show all versions of kotlinx-serialization-protobuf
Kotlin multiplatform serialization runtime library
The newest version!
/*
* Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:OptIn(ExperimentalSerializationApi::class)
package kotlinx.serialization.protobuf.internal
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.modules.*
import kotlinx.serialization.protobuf.*
internal typealias ProtoDesc = Long
internal enum class ProtoWireType(val typeId: Int) {
INVALID(-1),
VARINT(0),
i64(1),
SIZE_DELIMITED(2),
i32(5),
;
companion object {
fun from(typeId: Int): ProtoWireType {
return ProtoWireType.entries.find { it.typeId == typeId } ?: INVALID
}
}
fun wireIntWithTag(tag: Int): Int {
return ((tag shl 3) or typeId)
}
override fun toString(): String {
return "${this.name}($typeId)"
}
}
internal const val ID_HOLDER_ONE_OF = -2
private const val ONEOFMASK = 1L shl 36
private const val INTTYPEMASK = 3L shl 33
private const val PACKEDMASK = 1L shl 32
@Suppress("NOTHING_TO_INLINE")
internal inline fun ProtoDesc(protoId: Int, type: ProtoIntegerType, packed: Boolean = false, oneOf: Boolean = false): ProtoDesc {
val packedBits = if (packed) PACKEDMASK else 0L
val oneOfBits = if (oneOf) ONEOFMASK else 0L
return packedBits or oneOfBits or type.signature or protoId.toLong()
}
internal inline val ProtoDesc.protoId: Int get() = (this and Int.MAX_VALUE.toLong()).toInt()
internal val ProtoDesc.integerType: ProtoIntegerType
get() = when(this and INTTYPEMASK) {
ProtoIntegerType.DEFAULT.signature -> ProtoIntegerType.DEFAULT
ProtoIntegerType.SIGNED.signature -> ProtoIntegerType.SIGNED
else -> ProtoIntegerType.FIXED
}
internal val SerialDescriptor.isPackable: Boolean
@OptIn(kotlinx.serialization.ExperimentalSerializationApi::class)
get() = when (kind) {
PrimitiveKind.STRING,
!is PrimitiveKind -> false
else -> true
}
internal val ProtoDesc.isPacked: Boolean
get() = (this and PACKEDMASK) != 0L
internal val ProtoDesc.isOneOf: Boolean
get() = (this and ONEOFMASK) != 0L
internal fun ProtoDesc.overrideId(protoId: Int): ProtoDesc {
return this and (0xFFFFFFF00000000L) or protoId.toLong()
}
internal fun SerialDescriptor.extractParameters(index: Int): ProtoDesc {
val annotations = getElementAnnotations(index)
var protoId: Int = index + 1
var format: ProtoIntegerType = ProtoIntegerType.DEFAULT
var protoPacked = false
var isOneOf = false
for (i in annotations.indices) { // Allocation-friendly loop
val annotation = annotations[i]
if (annotation is ProtoNumber) {
protoId = annotation.number
checkFieldNumber(protoId, i, this)
} else if (annotation is ProtoType) {
format = annotation.type
} else if (annotation is ProtoPacked) {
protoPacked = true
} else if (annotation is ProtoOneOf) {
isOneOf = true
}
}
if (isOneOf) {
// reset protoId to index-based for oneOf field,
// Decoder will restore the real proto id then from [ProtobufDecoder.index2IdMap]
// See [kotlinx.serialization.protobuf.internal.ProtobufDecoder.decodeElementIndex] for detail
protoId = index + 1
}
return ProtoDesc(protoId, format, protoPacked, isOneOf)
}
/**
* Get the proto id from the descriptor of [index] element,
* or return [ID_HOLDER_ONE_OF] if such element is marked with [ProtoOneOf]
*/
internal fun extractProtoId(descriptor: SerialDescriptor, index: Int, zeroBasedDefault: Boolean): Int {
val annotations = descriptor.getElementAnnotations(index)
var result = if (zeroBasedDefault) index else index + 1
for (i in annotations.indices) { // Allocation-friendly loop
val annotation = annotations[i]
if (annotation is ProtoOneOf) {
// Fast return for one of field
return ID_HOLDER_ONE_OF
} else if (annotation is ProtoNumber) {
result = annotation.number
// 0 or negative numbers are acceptable for enums
if (!zeroBasedDefault) {
checkFieldNumber(result, i, descriptor)
}
}
}
return result
}
private fun checkFieldNumber(fieldNumber: Int, propertyIndex: Int, descriptor: SerialDescriptor) {
if (fieldNumber <= 0) {
throw SerializationException("$fieldNumber is not allowed in ProtoNumber for property '${descriptor.getElementName(propertyIndex)}' of '${descriptor.serialName}', because protobuf supports field numbers in range 1..${Int.MAX_VALUE}")
}
}
internal class ProtobufDecodingException(message: String, e: Throwable? = null) : SerializationException(message, e)
internal expect fun Int.reverseBytes(): Int
internal expect fun Long.reverseBytes(): Long
internal fun SerialDescriptor.getAllOneOfSerializerOfField(
serializersModule: SerializersModule,
): List {
return when (this.kind) {
PolymorphicKind.OPEN -> serializersModule.getPolymorphicDescriptors(this)
PolymorphicKind.SEALED -> getElementDescriptor(1).elementDescriptors.toList()
else -> throw IllegalArgumentException("Class ${this.serialName} should be abstract or sealed or interface to be used as @ProtoOneOf property.")
}.onEach { desc ->
if (desc.getElementAnnotations(0).none { anno -> anno is ProtoNumber }) {
throw IllegalArgumentException("${desc.serialName} implementing oneOf type ${this.serialName} should have @ProtoNumber annotation in its single property.")
}
}
}
internal fun SerialDescriptor.getActualOneOfSerializer(
serializersModule: SerializersModule,
protoId: Int
): SerialDescriptor? {
return getAllOneOfSerializerOfField(serializersModule).find { it.extractParameters(0).protoId == protoId }
}