Skip to content

Commit 0ced40f

Browse files
comments
1 parent 4150215 commit 0ced40f

File tree

2 files changed

+87
-22
lines changed

2 files changed

+87
-22
lines changed

formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/ProtoUnknownFields.kt

Lines changed: 74 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,33 +9,62 @@ import kotlinx.serialization.protobuf.internal.*
99
import kotlinx.serialization.protobuf.internal.ProtoWireType
1010

1111
/**
12-
* Mark a property as a holder for unknown fields in protobuf message.
12+
* Mark a property with type [ProtoMessage] as a holder for unknown fields in protobuf message.
13+
*
14+
* All the contents with unregistered proto number will be stored in this field.
1315
*/
1416
@SerialInfo
1517
@Target(AnnotationTarget.PROPERTY)
1618
@ExperimentalSerializationApi
1719
public annotation class ProtoUnknownFields
1820

21+
/**
22+
* Represents a protobuf message.
23+
*
24+
* Especially used as a holder of unknown proto fields in an arbitrary protobuf message.
25+
*/
1926
@Serializable(with = ProtoMessageSerializer::class)
2027
public class ProtoMessage internal constructor(
21-
public val fields: List<ProtoField>
28+
internal val fields: List<ProtoField>
2229
) {
2330
public companion object {
31+
/**
32+
* An empty [ProtoMessage] instance.
33+
*
34+
* Useful as a default value for [ProtoUnknownFields] properties.
35+
*/
2436
public val Empty: ProtoMessage = ProtoMessage(emptyList())
2537
}
2638

39+
/**
40+
* Number of fields holding in the message.
41+
*/
2742
public val size: Int get() = fields.size
28-
public fun asByteArray(): ByteArray = fields.fold(ByteArray(0)) { acc, protoField -> acc + protoField.asWireContent() }
2943

30-
public constructor(vararg fields: ProtoField) : this(fields.toList())
44+
/**
45+
* Returns a byte array representing of the message.
46+
*/
47+
public fun asByteArray(): ByteArray =
48+
fields.fold(ByteArray(0)) { acc, protoField -> acc + protoField.asWireContent() }
49+
50+
internal constructor(vararg fields: ProtoField) : this(fields.toList())
3151

52+
/**
53+
* Merges two [ProtoMessage] instances.
54+
*/
3255
public operator fun plus(other: ProtoMessage): ProtoMessage = merge(other)
3356

57+
/**
58+
* Merges two [ProtoMessage] instances.
59+
*/
3460
public fun merge(other: ProtoMessage): ProtoMessage {
3561
return ProtoMessage(fields + other.fields)
3662
}
3763

38-
public fun merge(vararg field: ProtoField): ProtoMessage {
64+
/**
65+
* Convenience method to merge multiple [ProtoField] with this message.
66+
*/
67+
internal fun merge(vararg field: ProtoField): ProtoMessage {
3968
return ProtoMessage(fields + field)
4069
}
4170

@@ -53,6 +82,9 @@ public class ProtoMessage internal constructor(
5382
}
5483
}
5584

85+
/**
86+
* Convenience method to merge two nullable [ProtoMessage] instances.
87+
*/
5688
public fun ProtoMessage?.merge(other: ProtoMessage?): ProtoMessage {
5789
return when {
5890
this == null -> other ?: ProtoMessage.Empty
@@ -61,29 +93,35 @@ public fun ProtoMessage?.merge(other: ProtoMessage?): ProtoMessage {
6193
}
6294
}
6395

64-
public fun ProtoMessage?.merge(vararg fields: ProtoField): ProtoMessage {
96+
/**
97+
* Convenience method to merge multiple [ProtoField] with a nullable [ProtoMessage].
98+
*/
99+
internal fun ProtoMessage?.merge(vararg fields: ProtoField): ProtoMessage {
65100
return when {
66101
this == null -> ProtoMessage(fields.toList())
67102
else -> this.merge(ProtoMessage(fields.toList()))
68103
}
69104
}
70105

106+
/**
107+
* Represents a single field in a protobuf message.
108+
*/
71109
@OptIn(ExperimentalSerializationApi::class)
72110
@Serializable(with = ProtoFieldSerializer::class)
73111
@KeepGeneratedSerializer
74112
@ConsistentCopyVisibility
75-
public data class ProtoField internal constructor(
113+
internal data class ProtoField internal constructor(
76114
internal val id: Int,
77115
internal val wireType: ProtoWireType,
78116
internal val data: ProtoContentHolder
79117
) {
80-
public companion object {
81-
public val Empty: ProtoField = ProtoField(0, ProtoWireType.INVALID, ProtoContentHolder.ByteArrayContent(ByteArray(0)))
118+
companion object {
119+
val Empty: ProtoField = ProtoField(0, ProtoWireType.INVALID, ProtoContentHolder.ByteArrayContent(ByteArray(0)))
82120
}
83121

84-
public fun asWireContent(): ByteArray = byteArrayOf(((id shl 3) or wireType.typeId).toByte()) + data.byteArray
122+
fun asWireContent(): ByteArray = byteArrayOf(((id shl 3) or wireType.typeId).toByte()) + data.byteArray
85123

86-
public val contentLength: Int
124+
val contentLength: Int
87125
get() = asWireContent().size
88126

89127
override fun equals(other: Any?): Boolean {
@@ -107,9 +145,19 @@ public data class ProtoField internal constructor(
107145
}
108146
}
109147

148+
/**
149+
* A data representation of a protobuf field in [ProtoField.data], without the field number and wire type.
150+
*/
110151
internal sealed interface ProtoContentHolder {
152+
153+
/**
154+
* Returns a byte array representation of the content.
155+
*/
111156
val byteArray: ByteArray
112157

158+
/**
159+
* Represents the content in raw byte array.
160+
*/
113161
data class ByteArrayContent(override val byteArray: ByteArray) : ProtoContentHolder {
114162
override fun equals(other: Any?): Boolean {
115163
return other is ProtoContentHolder && this.contentEquals(other)
@@ -120,6 +168,9 @@ internal sealed interface ProtoContentHolder {
120168
}
121169
}
122170

171+
/**
172+
* Represents the content with a nested [ProtoMessage].
173+
*/
123174
data class MessageContent(val content: ProtoMessage) : ProtoContentHolder {
124175
override val byteArray: ByteArray
125176
get() = content.asByteArray()
@@ -134,15 +185,27 @@ internal sealed interface ProtoContentHolder {
134185
}
135186
}
136187

188+
/**
189+
* Creates a [ProtoContentHolder] instance with a byte array content.
190+
*/
137191
internal fun ProtoContentHolder(content: ByteArray): ProtoContentHolder = ProtoContentHolder.ByteArrayContent(content)
138192

193+
/**
194+
* Get the length in bytes of the content in the [ProtoContentHolder].
195+
*/
139196
internal val ProtoContentHolder.contentLength: Int
140197
get() = byteArray.size
141198

199+
/**
200+
* Checks if the content of two [ProtoContentHolder] instances are equal.
201+
*/
142202
internal fun ProtoContentHolder.contentEquals(other: ProtoContentHolder): Boolean {
143203
return byteArray.contentEquals(other.byteArray)
144204
}
145205

206+
/**
207+
* Calculates the hash code of the content in the [ProtoContentHolder].
208+
*/
146209
internal fun ProtoContentHolder.contentHashCode(): Int {
147210
return byteArray.contentHashCode()
148211
}

formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/internal/ProtobufDecoding.kt

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ internal open class ProtobufDecoder(
2828
private var indexCache: IntArray? = null
2929
private var sparseIndexCache: MutableMap<Int, Int>? = null
3030

31-
// Index -> proto id for oneof element. An oneof element of certain index may refer to different proto id in runtime.
31+
// Index -> proto id for oneof element or unknown fields.
32+
// These kind of elements in certain index may refer to different proto id in runtime.
3233
private var index2IdMap: MutableMap<Int, Int>? = null
3334

3435
private var unknownHolderIndex: Int = -1
@@ -57,7 +58,7 @@ internal open class ProtobufDecoder(
5758
val cache = IntArray(elements + 1) { -1 }
5859
for (i in 0 until elements) {
5960
val protoId = extractProtoId(descriptor, i, false)
60-
// If any element is marked as ProtoOneOf on Unknown field holder,
61+
// If any element is marked as ProtoOneOf or Unknown field holder,
6162
// the fast path is not applicable
6263
// because num of id does not match the elements
6364
if (protoId in 0..elements) {
@@ -74,7 +75,7 @@ internal open class ProtobufDecoder(
7475

7576
private fun populateCacheMap(descriptor: SerialDescriptor, elements: Int) {
7677
val map = HashMap<Int, Int>(elements, 1f)
77-
var oneOfCount = 0
78+
var mapSize = 0
7879
for (i in 0 until elements) {
7980
val id = extractProtoId(descriptor, i, false)
8081
when (id) {
@@ -83,22 +84,22 @@ internal open class ProtobufDecoder(
8384
.getAllOneOfSerializerOfField(serializersModule)
8485
.map { it.extractParameters(0).protoId }
8586
.forEach { map.putProtoId(it, i) }
86-
oneOfCount ++
87+
mapSize ++
8788
}
8889
ID_HOLDER_UNKNOWN_FIELDS -> {
8990
require(unknownHolderIndex == -1) {
9091
"Only one unknown fields holder is allowed in a message"
9192
}
92-
oneOfCount ++
93+
mapSize ++
9394
unknownHolderIndex = i
9495
}
9596
else -> {
9697
map.putProtoId(id, i)
9798
}
9899
}
99100
}
100-
if (oneOfCount > 0) {
101-
index2IdMap = HashMap(oneOfCount, 1f)
101+
if (mapSize > 0) {
102+
index2IdMap = HashMap(mapSize, 1f)
102103
}
103104
sparseIndexCache = map
104105
}
@@ -338,12 +339,13 @@ internal open class ProtobufDecoder(
338339
val tag = descriptor.extractParameters(index)
339340
if (tag.isOneOf || tag.isUnknown) {
340341
/**
341-
* While decoding message with one-of field,
342+
* While decoding message with one-of field or unknown fields,
342343
* the proto id read from wire data cannot be easily found
343344
* in the properties of this type,
344-
* So the index of this one-of property and the id read from the wire
345-
* are saved in this map, then restored in [beginStructure]
346-
* and passed to [OneOfPolymorphicReader] to get the actual deserializer.
345+
* So the index of this property and the id read from the wire
346+
* are saved in this map, then
347+
* 1. restored in [beginStructure] and passed to [OneOfPolymorphicReader] to get the actual deserializer, or
348+
* 2. restored in [decodeUnknownFields] to get the right proto id for the unknown fields.
347349
*/
348350
index2IdMap?.put(index, protoId)
349351
}

0 commit comments

Comments
 (0)