All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.kotlinx.serialization.protobuf.internal.Helpers.kt Maven / Gradle / Ivy

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 }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy