1 /*
<lambda>null2 * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3 */
4
5 @file:OptIn(ExperimentalSerializationApi::class)
6
7 package kotlinx.serialization.protobuf.internal
8
9 import kotlinx.serialization.*
10 import kotlinx.serialization.descriptors.*
11 import kotlinx.serialization.modules.*
12 import kotlinx.serialization.protobuf.*
13
14 internal typealias ProtoDesc = Long
15
16 internal enum class ProtoWireType(val typeId: Int) {
17 INVALID(-1),
18 VARINT(0),
19 i64(1),
20 SIZE_DELIMITED(2),
21 i32(5),
22 ;
23
24 companion object {
25 fun from(typeId: Int): ProtoWireType {
26 return ProtoWireType.entries.find { it.typeId == typeId } ?: INVALID
27 }
28 }
29
30 fun wireIntWithTag(tag: Int): Int {
31 return ((tag shl 3) or typeId)
32 }
33
34 override fun toString(): String {
35 return "${this.name}($typeId)"
36 }
37 }
38
39 internal const val ID_HOLDER_ONE_OF = -2
40
41 private const val ONEOFMASK = 1L shl 36
42 private const val INTTYPEMASK = 3L shl 33
43 private const val PACKEDMASK = 1L shl 32
44
45 @Suppress("NOTHING_TO_INLINE")
ProtoDescnull46 internal inline fun ProtoDesc(protoId: Int, type: ProtoIntegerType, packed: Boolean = false, oneOf: Boolean = false): ProtoDesc {
47 val packedBits = if (packed) PACKEDMASK else 0L
48 val oneOfBits = if (oneOf) ONEOFMASK else 0L
49 return packedBits or oneOfBits or type.signature or protoId.toLong()
50 }
51
52 internal inline val ProtoDesc.protoId: Int get() = (this and Int.MAX_VALUE.toLong()).toInt()
53
54 internal val ProtoDesc.integerType: ProtoIntegerType
55 get() = when(this and INTTYPEMASK) {
56 ProtoIntegerType.DEFAULT.signature -> ProtoIntegerType.DEFAULT
57 ProtoIntegerType.SIGNED.signature -> ProtoIntegerType.SIGNED
58 else -> ProtoIntegerType.FIXED
59 }
60
61 internal val SerialDescriptor.isPackable: Boolean
62 @OptIn(kotlinx.serialization.ExperimentalSerializationApi::class)
63 get() = when (kind) {
64 PrimitiveKind.STRING,
65 !is PrimitiveKind -> false
66 else -> true
67 }
68
69 internal val ProtoDesc.isPacked: Boolean
70 get() = (this and PACKEDMASK) != 0L
71
72 internal val ProtoDesc.isOneOf: Boolean
73 get() = (this and ONEOFMASK) != 0L
74
overrideIdnull75 internal fun ProtoDesc.overrideId(protoId: Int): ProtoDesc {
76 return this and (0xFFFFFFF00000000L) or protoId.toLong()
77 }
78
extractParametersnull79 internal fun SerialDescriptor.extractParameters(index: Int): ProtoDesc {
80 val annotations = getElementAnnotations(index)
81 var protoId: Int = index + 1
82 var format: ProtoIntegerType = ProtoIntegerType.DEFAULT
83 var protoPacked = false
84 var isOneOf = false
85
86 for (i in annotations.indices) { // Allocation-friendly loop
87 val annotation = annotations[i]
88 if (annotation is ProtoNumber) {
89 protoId = annotation.number
90 checkFieldNumber(protoId, i, this)
91 } else if (annotation is ProtoType) {
92 format = annotation.type
93 } else if (annotation is ProtoPacked) {
94 protoPacked = true
95 } else if (annotation is ProtoOneOf) {
96 isOneOf = true
97 }
98 }
99 if (isOneOf) {
100 // reset protoId to index-based for oneOf field,
101 // Decoder will restore the real proto id then from [ProtobufDecoder.index2IdMap]
102 // See [kotlinx.serialization.protobuf.internal.ProtobufDecoder.decodeElementIndex] for detail
103 protoId = index + 1
104 }
105 return ProtoDesc(protoId, format, protoPacked, isOneOf)
106 }
107
108 /**
109 * Get the proto id from the descriptor of [index] element,
110 * or return [ID_HOLDER_ONE_OF] if such element is marked with [ProtoOneOf]
111 */
extractProtoIdnull112 internal fun extractProtoId(descriptor: SerialDescriptor, index: Int, zeroBasedDefault: Boolean): Int {
113 val annotations = descriptor.getElementAnnotations(index)
114 var result = if (zeroBasedDefault) index else index + 1
115 for (i in annotations.indices) { // Allocation-friendly loop
116 val annotation = annotations[i]
117 if (annotation is ProtoOneOf) {
118 // Fast return for one of field
119 return ID_HOLDER_ONE_OF
120 } else if (annotation is ProtoNumber) {
121 result = annotation.number
122 // 0 or negative numbers are acceptable for enums
123 if (!zeroBasedDefault) {
124 checkFieldNumber(result, i, descriptor)
125 }
126 }
127 }
128 return result
129 }
130
checkFieldNumbernull131 private fun checkFieldNumber(fieldNumber: Int, propertyIndex: Int, descriptor: SerialDescriptor) {
132 if (fieldNumber <= 0) {
133 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}")
134 }
135 }
136
137 internal class ProtobufDecodingException(message: String, e: Throwable? = null) : SerializationException(message, e)
138
reverseBytesnull139 internal expect fun Int.reverseBytes(): Int
140 internal expect fun Long.reverseBytes(): Long
141
142
143 internal fun SerialDescriptor.getAllOneOfSerializerOfField(
144 serializersModule: SerializersModule,
145 ): List<SerialDescriptor> {
146 return when (this.kind) {
147 PolymorphicKind.OPEN -> serializersModule.getPolymorphicDescriptors(this)
148 PolymorphicKind.SEALED -> getElementDescriptor(1).elementDescriptors.toList()
149 else -> throw IllegalArgumentException("Class ${this.serialName} should be abstract or sealed or interface to be used as @ProtoOneOf property.")
150 }.onEach { desc ->
151 if (desc.getElementAnnotations(0).none { anno -> anno is ProtoNumber }) {
152 throw IllegalArgumentException("${desc.serialName} implementing oneOf type ${this.serialName} should have @ProtoNumber annotation in its single property.")
153 }
154 }
155 }
156
getActualOneOfSerializernull157 internal fun SerialDescriptor.getActualOneOfSerializer(
158 serializersModule: SerializersModule,
159 protoId: Int
160 ): SerialDescriptor? {
161 return getAllOneOfSerializerOfField(serializersModule).find { it.extractParameters(0).protoId == protoId }
162 }
163