Skip to content

Commit 775b929

Browse files
committed
fix parsing of cbor elements as members
1 parent 9ecf291 commit 775b929

File tree

4 files changed

+83
-42
lines changed

4 files changed

+83
-42
lines changed

formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/CborElementSerializers.kt

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ import kotlinx.serialization.cbor.*
1111
import kotlinx.serialization.descriptors.*
1212
import kotlinx.serialization.encoding.*
1313

14+
internal interface CborSerializer
15+
1416
/**
1517
* Serializer object providing [SerializationStrategy] and [DeserializationStrategy] for [CborElement].
1618
* It can only be used by with [Cbor] format and its input ([CborDecoder] and [CborEncoder]).
1719
*/
18-
internal object CborElementSerializer : KSerializer<CborElement> {
20+
internal object CborElementSerializer : KSerializer<CborElement>, CborSerializer {
1921
override val descriptor: SerialDescriptor =
2022
buildSerialDescriptor("kotlinx.serialization.cbor.CborElement", PolymorphicKind.SEALED) {
2123
// Resolve cyclic dependency in descriptors by late binding
@@ -52,7 +54,7 @@ internal object CborElementSerializer : KSerializer<CborElement> {
5254
* Serializer object providing [SerializationStrategy] and [DeserializationStrategy] for [CborPrimitive].
5355
* It can only be used by with [Cbor] format an its input ([CborDecoder] and [CborEncoder]).
5456
*/
55-
internal object CborPrimitiveSerializer : KSerializer<CborPrimitive<*>> {
57+
internal object CborPrimitiveSerializer : KSerializer<CborPrimitive<*>>, CborSerializer {
5658
override val descriptor: SerialDescriptor =
5759
buildSerialDescriptor("kotlinx.serialization.cbor.CborPrimitive", PolymorphicKind.SEALED)
5860

@@ -79,7 +81,7 @@ internal object CborPrimitiveSerializer : KSerializer<CborPrimitive<*>> {
7981
* Serializer object providing [SerializationStrategy] and [DeserializationStrategy] for [CborNull].
8082
* It can only be used by with [Cbor] format an its input ([CborDecoder] and [CborEncoder]).
8183
*/
82-
internal object CborNullSerializer : KSerializer<CborNull> {
84+
internal object CborNullSerializer : KSerializer<CborNull>, CborSerializer {
8385

8486
override val descriptor: SerialDescriptor =
8587
buildSerialDescriptor("kotlinx.serialization.cbor.CborNull", SerialKind.ENUM)
@@ -102,7 +104,7 @@ internal object CborNullSerializer : KSerializer<CborNull> {
102104
}
103105

104106

105-
internal object CborIntSerializer : KSerializer<CborInt<*>> {
107+
internal object CborIntSerializer : KSerializer<CborInt<*>>, CborSerializer {
106108
override val descriptor: SerialDescriptor =
107109
PrimitiveSerialDescriptor("kotlinx.serialization.cbor.CborInt", PrimitiveKind.LONG)
108110

@@ -120,7 +122,7 @@ internal object CborIntSerializer : KSerializer<CborInt<*>> {
120122
}
121123
}
122124

123-
internal object CborNegativeIntSerializer : KSerializer<CborNegativeInt> {
125+
internal object CborNegativeIntSerializer : KSerializer<CborNegativeInt>, CborSerializer {
124126
override val descriptor: SerialDescriptor =
125127
PrimitiveSerialDescriptor("kotlinx.serialization.cbor.CborNegativeInt", PrimitiveKind.LONG)
126128

@@ -136,7 +138,7 @@ internal object CborNegativeIntSerializer : KSerializer<CborNegativeInt> {
136138
}
137139
}
138140

139-
internal object CborPositiveIntSerializer : KSerializer<CborPositiveInt> {
141+
internal object CborPositiveIntSerializer : KSerializer<CborPositiveInt>, CborSerializer {
140142
override val descriptor: SerialDescriptor =
141143
PrimitiveSerialDescriptor("kotlinx.serialization.cbor.CborPositiveInt", PrimitiveKind.LONG)
142144

@@ -152,7 +154,7 @@ internal object CborPositiveIntSerializer : KSerializer<CborPositiveInt> {
152154
}
153155
}
154156

155-
internal object CborDoubleSerializer : KSerializer<CborDouble> {
157+
internal object CborDoubleSerializer : KSerializer<CborDouble>, CborSerializer {
156158
override val descriptor: SerialDescriptor =
157159
PrimitiveSerialDescriptor("kotlinx.serialization.cbor.CborDouble", PrimitiveKind.DOUBLE)
158160

@@ -172,7 +174,7 @@ internal object CborDoubleSerializer : KSerializer<CborDouble> {
172174
* Serializer object providing [SerializationStrategy] and [DeserializationStrategy] for [CborString].
173175
* It can only be used by with [Cbor] format an its input ([CborDecoder] and [CborEncoder]).
174176
*/
175-
internal object CborStringSerializer : KSerializer<CborString> {
177+
internal object CborStringSerializer : KSerializer<CborString>, CborSerializer {
176178
override val descriptor: SerialDescriptor =
177179
PrimitiveSerialDescriptor("kotlinx.serialization.cbor.CborString", PrimitiveKind.STRING)
178180

@@ -194,7 +196,7 @@ internal object CborStringSerializer : KSerializer<CborString> {
194196
* Serializer object providing [SerializationStrategy] and [DeserializationStrategy] for [CborBoolean].
195197
* It can only be used by with [Cbor] format an its input ([CborDecoder] and [CborEncoder]).
196198
*/
197-
internal object CborBooleanSerializer : KSerializer<CborBoolean> {
199+
internal object CborBooleanSerializer : KSerializer<CborBoolean>, CborSerializer {
198200
override val descriptor: SerialDescriptor =
199201
PrimitiveSerialDescriptor("kotlinx.serialization.cbor.CborBoolean", PrimitiveKind.BOOLEAN)
200202

@@ -216,7 +218,7 @@ internal object CborBooleanSerializer : KSerializer<CborBoolean> {
216218
* Serializer object providing [SerializationStrategy] and [DeserializationStrategy] for [CborByteString].
217219
* It can only be used by with [Cbor] format and its input ([CborDecoder] and [CborEncoder]).
218220
*/
219-
internal object CborByteStringSerializer : KSerializer<CborByteString> {
221+
internal object CborByteStringSerializer : KSerializer<CborByteString>, CborSerializer {
220222
override val descriptor: SerialDescriptor =
221223
PrimitiveSerialDescriptor("kotlinx.serialization.cbor.CborByteString", PrimitiveKind.STRING)
222224

@@ -238,7 +240,7 @@ internal object CborByteStringSerializer : KSerializer<CborByteString> {
238240
* Serializer object providing [SerializationStrategy] and [DeserializationStrategy] for [CborMap].
239241
* It can only be used by with [Cbor] format and its input ([CborDecoder] and [CborEncoder]).
240242
*/
241-
internal object CborMapSerializer : KSerializer<CborMap> {
243+
internal object CborMapSerializer : KSerializer<CborMap>, CborSerializer {
242244
private object CborMapDescriptor :
243245
SerialDescriptor by MapSerializer(CborElementSerializer, CborElementSerializer).descriptor {
244246
@ExperimentalSerializationApi
@@ -263,7 +265,7 @@ internal object CborMapSerializer : KSerializer<CborMap> {
263265
* Serializer object providing [SerializationStrategy] and [DeserializationStrategy] for [CborList].
264266
* It can only be used by with [Cbor] format an its input ([CborDecoder] and [CborEncoder]).
265267
*/
266-
internal object CborListSerializer : KSerializer<CborList> {
268+
internal object CborListSerializer : KSerializer<CborList>, CborSerializer {
267269
private object CborListDescriptor : SerialDescriptor by ListSerializer(CborElementSerializer).descriptor {
268270
@ExperimentalSerializationApi
269271
override val serialName: String = "kotlinx.serialization.cbor.CborList"

formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/CborParserInterface.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package kotlinx.serialization.cbor.internal
77

88
import kotlinx.serialization.*
9+
import kotlinx.serialization.cbor.CborList
910

1011
/**
1112
* Common interface for CBOR parsers that can read CBOR data from different sources.
@@ -38,4 +39,6 @@ internal sealed interface CborParserInterface {
3839

3940
// Tag verification
4041
fun verifyTagsAndThrow(expected: ULongArray, actual: ULongArray?)
42+
43+
fun processTags(tags: ULongArray?): ULongArray?
4144
}

formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Decoder.kt

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ internal open class CborReader(override val cbor: Cbor, protected val parser: Cb
1818
override fun decodeCborElement(): CborElement =
1919
when (parser) {
2020
is CborParser -> CborTreeReader(cbor.configuration, parser).read()
21-
is StructuredCborParser -> parser.element
21+
is StructuredCborParser -> parser.layer.currentElement
2222
}
2323

2424

@@ -116,7 +116,12 @@ internal open class CborReader(override val cbor: Cbor, protected val parser: Cb
116116

117117
@OptIn(ExperimentalSerializationApi::class)
118118
override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T {
119-
return if ((decodeByteArrayAsByteString || cbor.configuration.alwaysUseByteString)
119+
@Suppress("UNCHECKED_CAST")
120+
return if (deserializer is CborSerializer) {
121+
parser.processTags(tags)
122+
decodeCborElement() as T
123+
}
124+
else if ((decodeByteArrayAsByteString || cbor.configuration.alwaysUseByteString)
120125
&& deserializer.descriptor == ByteArraySerializer().descriptor
121126
) {
122127
@Suppress("UNCHECKED_CAST")
@@ -283,7 +288,7 @@ internal class CborParser(private val input: ByteArrayInput, private val verifyO
283288
input.readExactNBytes(strLen)
284289
}
285290

286-
private fun processTags(tags: ULongArray?): ULongArray? {
291+
override fun processTags(tags: ULongArray?): ULongArray? {
287292
var index = 0
288293
val collectedTags = mutableListOf<ULong>()
289294
while ((curByte and 0b111_00000) == HEADER_TAG) {
@@ -559,7 +564,7 @@ private fun Iterable<ByteArray>.flatten(): ByteArray {
559564
* Works for single elements (where current is directly set to the element) and for collections (where current
560565
* will be first set after `startMap` or `startArray`
561566
*/
562-
private class PeekingIterator<T : Any>(private val iter: ListIterator<T>) : Iterator<T> by iter {
567+
internal class PeekingIterator<T : Any>(private val iter: ListIterator<T>) : Iterator<T> by iter {
563568

564569
lateinit var current: T
565570
private set
@@ -580,6 +585,7 @@ private class PeekingIterator<T : Any>(private val iter: ListIterator<T>) : Iter
580585
/** Maps are a bit special for nullability checks, so we need to check whether we're in a map or not*/
581586
private typealias CborLayer = Pair<Boolean, PeekingIterator<CborElement>>
582587

588+
//TODO get rid of this!
583589
private val CborLayer.isMap get() = first
584590

585591
/** current element reference inside a layer. If the layer is a primitive then the current element is the layer itself*/
@@ -595,7 +601,8 @@ private fun CborLayer.nextElement() = second.next()
595601
internal class StructuredCborParser(internal val element: CborElement, private val verifyObjectTags: Boolean) :
596602
CborParserInterface {
597603

598-
private var layer: CborLayer = false to PeekingIterator(element)
604+
internal var layer: CborLayer = false to PeekingIterator(element)
605+
private set
599606

600607

601608
private val layerStack = ArrayDeque<CborLayer>()
@@ -605,7 +612,7 @@ internal class StructuredCborParser(internal val element: CborElement, private v
605612
override fun isNull() =
606613
if (layer.isMap) layer.peek().let {
607614
it is CborNull ||
608-
/*THIS IS NOT CBOR-COMPLIANT but KxS-proprietary handling of nullable classes*/
615+
/*THIS IS NOT CBOR-COMPLIANT but KxS-proprietary handling of nullable classes*/
609616
(it is CborMap && it.isEmpty())
610617
} else layer.currentElement is CborNull
611618

@@ -647,7 +654,7 @@ internal class StructuredCborParser(internal val element: CborElement, private v
647654
processTags(tags)
648655
if (layer.currentElement !is CborNull) {
649656
/* THIS IS NOT CBOR-COMPLIANT but KxS-proprietary handling of nullable classes*/
650-
if(layer.currentElement is CborMap && (layer.currentElement as CborMap).isEmpty())
657+
if (layer.currentElement is CborMap && (layer.currentElement as CborMap).isEmpty())
651658
return null
652659
throw CborDecodingException("Expected null, got ${layer.currentElement::class.simpleName}")
653660
}
@@ -715,7 +722,7 @@ internal class StructuredCborParser(internal val element: CborElement, private v
715722
* The reason this method mixes two behaviours is that decoding a primitive is invoked on a single element.
716723
* `decodeElementIndex`, etc. is invoked on an iterable and there are key tags and value tags
717724
*/
718-
private fun processTags(tags: ULongArray?): ULongArray? {
725+
override fun processTags(tags: ULongArray?): ULongArray? {
719726

720727
// If we're in a list/map, advance to the next element
721728
if (layer.hasNext()) layer.nextElement()

formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborElementTest.kt

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,6 @@ class CborElementTest {
1111

1212
private val cbor = Cbor {}
1313

14-
/**
15-
* Helper method to decode a hex string to a CborElement
16-
*/
17-
private fun decodeHexToCborElement(hexString: String): CborElement {
18-
val bytes = HexConverter.parseHexBinary(hexString.uppercase())
19-
return cbor.decodeFromByteArray(bytes)
20-
}
21-
2214
@Test
2315
fun testCborNull() {
2416
val nullElement = CborNull()
@@ -217,42 +209,41 @@ class CborElementTest {
217209
}
218210

219211
@Test
220-
fun testDecodeIntegers() {
212+
fun testDecodePositiveInt() {
221213
// Test data from CborParserTest.testParseIntegers
222-
val element = decodeHexToCborElement("0C") as CborPositiveInt
214+
val element = cbor.decodeFromHexString<CborElement>("0C") as CborPositiveInt
223215
assertEquals(12u, element.value)
224-
225216
}
226217

227218
@Test
228219
fun testDecodeStrings() {
229220
// Test data from CborParserTest.testParseStrings
230-
val element = decodeHexToCborElement("6568656C6C6F")
221+
val element = cbor.decodeFromHexString<CborElement>("6568656C6C6F")
231222
assertTrue(element is CborString)
232223
assertEquals("hello", element.value)
233224

234225
val longStringElement =
235-
decodeHexToCborElement("7828737472696E672074686174206973206C6F6E676572207468616E2032332063686172616374657273")
226+
cbor.decodeFromHexString<CborElement>("7828737472696E672074686174206973206C6F6E676572207468616E2032332063686172616374657273")
236227
assertTrue(longStringElement is CborString)
237228
assertEquals("string that is longer than 23 characters", longStringElement.value)
238229
}
239230

240231
@Test
241232
fun testDecodeFloatingPoint() {
242233
// Test data from CborParserTest.testParseDoubles
243-
val doubleElement = decodeHexToCborElement("fb7e37e43c8800759c")
234+
val doubleElement = cbor.decodeFromHexString<CborElement>("fb7e37e43c8800759c")
244235
assertTrue(doubleElement is CborDouble)
245236
assertEquals(1e+300, doubleElement.value)
246237

247-
val floatElement = decodeHexToCborElement("fa47c35000")
238+
val floatElement = cbor.decodeFromHexString<CborElement>("fa47c35000")
248239
assertTrue(floatElement is CborDouble)
249240
assertEquals(100000.0f, floatElement.value.toFloat())
250241
}
251242

252243
@Test
253244
fun testDecodeByteString() {
254245
// Test data from CborParserTest.testRfc7049IndefiniteByteStringExample
255-
val element = decodeHexToCborElement("5F44aabbccdd43eeff99FF")
246+
val element = cbor.decodeFromHexString<CborElement>("5F44aabbccdd43eeff99FF")
256247
assertTrue(element is CborByteString)
257248
val byteString = element as CborByteString
258249
val expectedBytes = HexConverter.parseHexBinary("aabbccddeeff99")
@@ -262,7 +253,7 @@ class CborElementTest {
262253
@Test
263254
fun testDecodeArray() {
264255
// Test data from CborParserTest.testSkipCollections
265-
val element = decodeHexToCborElement("830118ff1a00010000")
256+
val element = cbor.decodeFromHexString<CborElement>("830118ff1a00010000")
266257
assertTrue(element is CborList)
267258
val list = element as CborList
268259
assertEquals(3, list.size)
@@ -274,7 +265,7 @@ class CborElementTest {
274265
@Test
275266
fun testDecodeMap() {
276267
// Test data from CborParserTest.testSkipCollections
277-
val element = decodeHexToCborElement("a26178676b6f746c696e7861796d73657269616c697a6174696f6e")
268+
val element = cbor.decodeFromHexString<CborElement>("a26178676b6f746c696e7861796d73657269616c697a6174696f6e")
278269
assertTrue(element is CborMap)
279270
val map = element as CborMap
280271
assertEquals(2, map.size)
@@ -286,7 +277,7 @@ class CborElementTest {
286277
fun testDecodeComplexStructure() {
287278
// Test data from CborParserTest.testSkipIndefiniteLength
288279
val element =
289-
decodeHexToCborElement("a461615f42cafe43010203ff61627f6648656c6c6f2065776f726c64ff61639f676b6f746c696e786d73657269616c697a6174696f6eff6164bf613101613202613303ff")
280+
cbor.decodeFromHexString<CborElement>("a461615f42cafe43010203ff61627f6648656c6c6f2065776f726c64ff61639f676b6f746c696e786d73657269616c697a6174696f6eff6164bf613101613202613303ff")
290281
assertTrue(element is CborMap)
291282
val map = element as CborMap
292283
assertEquals(4, map.size)
@@ -313,8 +304,6 @@ class CborElementTest {
313304
assertEquals(CborPositiveInt(3u), nestedMap[CborString("3")])
314305
}
315306

316-
// Test removed due to incompatibility with the new tag implementation
317-
318307
@OptIn(ExperimentalStdlibApi::class)
319308
@Test
320309
fun testTagsRoundTrip() {
@@ -333,6 +322,46 @@ class CborElementTest {
333322
assertEquals(42u, decodedElement.tags.first())
334323
}
335324

336-
325+
@Test
326+
fun testGenericAndCborSpecificMixed() {
327+
Triple(
328+
Cbor {
329+
encodeValueTags = true
330+
encodeKeyTags = true
331+
verifyKeyTags = true
332+
verifyObjectTags = true
333+
},
334+
MixedBag(
335+
str = "A string, is a string, is a string",
336+
bStr = CborByteString(byteArrayOf()),
337+
cborElement = CborBoolean(false),
338+
cborPositiveInt = CborPositiveInt(1u),
339+
cborInt = CborInt(-1),
340+
tagged = 26
341+
),
342+
"bf6373747278224120737472696e672c206973206120737472696e672c206973206120737472696e676462537472406b63626f72456c656d656e74f46f63626f72506f736974697665496e74016763626f72496e7420d82a66746167676564d90921181aff"
343+
)
344+
.let { (cbor, obj, hex) ->
345+
val struct = cbor.encodeToCbor(obj)
346+
assertEquals(struct, cbor.decodeFromHexString<CborElement>(hex))
347+
assertEquals(obj, cbor.decodeFromCbor(struct))
348+
assertEquals(obj, cbor.decodeFromHexString(hex))
349+
assertEquals(hex, cbor.encodeToHexString(obj))
350+
assertEquals(hex, cbor.encodeToHexString(struct))
351+
352+
}
353+
}
337354

338355
}
356+
357+
@Serializable
358+
data class MixedBag(
359+
val str: String,
360+
val bStr: CborByteString?,
361+
val cborElement: CborElement,
362+
val cborPositiveInt: CborPrimitive<*>,
363+
val cborInt: CborInt<*>,
364+
@KeyTags(42u)
365+
@ValueTags(2337u)
366+
val tagged: Int
367+
)

0 commit comments

Comments
 (0)