Skip to content

Commit 85c5dbc

Browse files
committed
fix tagging
1 parent 5c37b6e commit 85c5dbc

File tree

5 files changed

+80
-42
lines changed

5 files changed

+80
-42
lines changed

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

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ internal object CborNullSerializer : KSerializer<CborNull> {
8585
buildSerialDescriptor("kotlinx.serialization.cbor.CborNull", SerialKind.ENUM)
8686

8787
override fun serialize(encoder: Encoder, value: CborNull) {
88-
val cborEncoder= encoder.asCborEncoder()
88+
val cborEncoder = encoder.asCborEncoder()
8989
cborEncoder.encodeTags(value)
9090
encoder.encodeNull()
9191
}
@@ -95,14 +95,16 @@ internal object CborNullSerializer : KSerializer<CborNull> {
9595
if (decoder.decodeNotNullMark()) {
9696
throw CborDecodingException("Expected 'null' literal")
9797
}
98+
9899
decoder.decodeNull()
99100
return CborNull()
100101
}
101102
}
102103

103104

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

107109
override fun serialize(encoder: Encoder, value: CborInt<*>) {
108110
when (value) {
@@ -119,10 +121,11 @@ internal object CborIntSerializer : KSerializer<CborInt<*>> {
119121
}
120122

121123
internal object CborNegativeIntSerializer : KSerializer<CborNegativeInt> {
122-
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("kotlinx.serialization.cbor.CborNegativeInt", PrimitiveKind.LONG)
124+
override val descriptor: SerialDescriptor =
125+
PrimitiveSerialDescriptor("kotlinx.serialization.cbor.CborNegativeInt", PrimitiveKind.LONG)
123126

124127
override fun serialize(encoder: Encoder, value: CborNegativeInt) {
125-
val cborEncoder= encoder.asCborEncoder()
128+
val cborEncoder = encoder.asCborEncoder()
126129
cborEncoder.encodeTags(value)
127130
encoder.encodeLong(value.value)
128131
}
@@ -134,10 +137,11 @@ internal object CborNegativeIntSerializer : KSerializer<CborNegativeInt> {
134137
}
135138

136139
internal object CborPositiveIntSerializer : KSerializer<CborPositiveInt> {
137-
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("kotlinx.serialization.cbor.CborPositiveInt", PrimitiveKind.LONG)
140+
override val descriptor: SerialDescriptor =
141+
PrimitiveSerialDescriptor("kotlinx.serialization.cbor.CborPositiveInt", PrimitiveKind.LONG)
138142

139143
override fun serialize(encoder: Encoder, value: CborPositiveInt) {
140-
val cborEncoder= encoder.asCborEncoder()
144+
val cborEncoder = encoder.asCborEncoder()
141145
cborEncoder.encodeTags(value)
142146
encoder.encodeInline(descriptor).encodeSerializableValue(ULong.serializer(), value.value as ULong)
143147
}
@@ -149,10 +153,11 @@ internal object CborPositiveIntSerializer : KSerializer<CborPositiveInt> {
149153
}
150154

151155
internal object CborDoubleSerializer : KSerializer<CborDouble> {
152-
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("kotlinx.serialization.cbor.CborDouble", PrimitiveKind.DOUBLE)
156+
override val descriptor: SerialDescriptor =
157+
PrimitiveSerialDescriptor("kotlinx.serialization.cbor.CborDouble", PrimitiveKind.DOUBLE)
153158

154159
override fun serialize(encoder: Encoder, value: CborDouble) {
155-
val cborEncoder= encoder.asCborEncoder()
160+
val cborEncoder = encoder.asCborEncoder()
156161
cborEncoder.encodeTags(value)
157162
encoder.encodeDouble(value.value)
158163
}
@@ -172,7 +177,7 @@ internal object CborStringSerializer : KSerializer<CborString> {
172177
PrimitiveSerialDescriptor("kotlinx.serialization.cbor.CborString", PrimitiveKind.STRING)
173178

174179
override fun serialize(encoder: Encoder, value: CborString) {
175-
val cborEncoder= encoder.asCborEncoder()
180+
val cborEncoder = encoder.asCborEncoder()
176181
cborEncoder.encodeTags(value)
177182
encoder.encodeString(value.value)
178183
}
@@ -194,7 +199,7 @@ internal object CborBooleanSerializer : KSerializer<CborBoolean> {
194199
PrimitiveSerialDescriptor("kotlinx.serialization.cbor.CborBoolean", PrimitiveKind.BOOLEAN)
195200

196201
override fun serialize(encoder: Encoder, value: CborBoolean) {
197-
val cborEncoder= encoder.asCborEncoder()
202+
val cborEncoder = encoder.asCborEncoder()
198203
cborEncoder.encodeTags(value)
199204
encoder.encodeBoolean(value.value)
200205
}
@@ -216,7 +221,7 @@ internal object CborByteStringSerializer : KSerializer<CborByteString> {
216221
PrimitiveSerialDescriptor("kotlinx.serialization.cbor.CborByteString", PrimitiveKind.STRING)
217222

218223
override fun serialize(encoder: Encoder, value: CborByteString) {
219-
val cborEncoder= encoder.asCborEncoder()
224+
val cborEncoder = encoder.asCborEncoder()
220225
cborEncoder.encodeTags(value)
221226
cborEncoder.encodeByteString(value.value)
222227
}

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -589,7 +589,7 @@ private fun CborLayer.hasNext() = second.hasNext()
589589
private fun CborLayer.nextElement() = second.next()
590590

591591
/**
592-
* CBOR parser that operates on [CborElement] instead of bytes. Closely mirrors the baheviour of [CborParser], so the
592+
* CBOR parser that operates on [CborElement] instead of bytes. Closely mirrors the behaviour of [CborParser], so the
593593
* [CborDecoder] can remain largely unchanged.
594594
*/
595595
internal class StructuredCborParser(internal val element: CborElement, private val verifyObjectTags: Boolean) :
@@ -602,7 +602,12 @@ internal class StructuredCborParser(internal val element: CborElement, private v
602602

603603
// map needs special treatment because keys and values are laid out as a list alternating between key and value to
604604
// mirror the byte-layout of a cbor map.
605-
override fun isNull() = if (layer.isMap) layer.peek() is CborNull else layer.currentElement is CborNull
605+
override fun isNull() =
606+
if (layer.isMap) layer.peek().let {
607+
it is CborNull ||
608+
/*THIS IS NOT CBOR-COMPLIANT but KxS-proprietary handling of nullable classes*/
609+
(it is CborMap && it.isEmpty())
610+
} else layer.currentElement is CborNull
606611

607612
override fun isEnd() = !layer.hasNext()
608613

@@ -618,7 +623,7 @@ internal class StructuredCborParser(internal val element: CborElement, private v
618623
}
619624
layerStack += layer
620625
val list = layer.currentElement as CborList
621-
layer = false to PeekingIterator(list.listIterator())
626+
layer = true to PeekingIterator(list.listIterator())
622627
return list.size //we could just return -1 and let the current layer run out of elements to never run into inconsistencies
623628
// if we do keep it like this, any inconsistencies serve as a canary for implementation bugs
624629
}
@@ -641,6 +646,9 @@ internal class StructuredCborParser(internal val element: CborElement, private v
641646
override fun nextNull(tags: ULongArray?): Nothing? {
642647
processTags(tags)
643648
if (layer.currentElement !is CborNull) {
649+
/* THIS IS NOT CBOR-COMPLIANT but KxS-proprietary handling of nullable classes*/
650+
if(layer.currentElement is CborMap && (layer.currentElement as CborMap).isEmpty())
651+
return null
644652
throw CborDecodingException("Expected null, got ${layer.currentElement::class.simpleName}")
645653
}
646654
return null

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

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -234,12 +234,20 @@ internal class StructuredCborWriter(cbor: Cbor) : CborWriter(cbor) {
234234

235235
private val stack = ArrayDeque<CborContainer>()
236236
private var currentElement: CborContainer? = null
237+
// value tags are collects inside beginStructure, so we need to cache them here and write them in beginStructure or encodeXXX
238+
// and then null them out, so there are no leftovers
239+
private var nextValueTags : ULongArray = ulongArrayOf()
240+
get() {
241+
val ret=field
242+
field=ulongArrayOf()
243+
return ret
244+
}
237245

238246
fun finalize() = currentElement!!.finalize()
239247

240248
override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder {
241249
//TODO check if cborelement and be done
242-
val tags = descriptor.getObjectTags() ?: ulongArrayOf()
250+
val tags = nextValueTags + (descriptor.getObjectTags() ?: ulongArrayOf())
243251
val element = if (descriptor.hasArrayTag()) {
244252
CborContainer.List(tags)
245253
} else {
@@ -270,8 +278,11 @@ internal class StructuredCborWriter(cbor: Cbor) : CborWriter(cbor) {
270278

271279

272280
override fun encodeElement(descriptor: SerialDescriptor, index: Int): Boolean {
273-
//we don't care for special encoding of an empty class, so we don't set this flag here
274-
// isClass = descriptor.getElementDescriptor(index).kind == StructureKind.CLASS
281+
// this mirrors the special encoding of nullable classes that are null into am empty map.
282+
// THIS IS NOT CBOR-COMPLiANT
283+
// but keeps backwards compatibility with the way kotlinx.serialization CBOR format has always worked.
284+
isClass = descriptor.getElementDescriptor(index).kind == StructureKind.CLASS
285+
275286
encodeByteArrayAsByteString = descriptor.isByteString(index)
276287
//TODO check if cborelement and be done
277288
val name = descriptor.getElementName(index)
@@ -291,59 +302,60 @@ internal class StructuredCborWriter(cbor: Cbor) : CborWriter(cbor) {
291302

292303
if (cbor.configuration.encodeValueTags) {
293304
descriptor.getValueTags(index)?.let { valueTags ->
294-
currentElement!!.tags += valueTags
305+
//collect them for late encoding in beginStructure or encodeXXX
306+
nextValueTags = valueTags?:ulongArrayOf()
295307
}
296308
}
297309
return true
298310
}
299311

300-
301312
override fun encodeBoolean(value: Boolean) {
302-
currentElement += CborBoolean(value)
313+
currentElement += CborBoolean(value, nextValueTags)
303314
}
304315

305316
override fun encodeByte(value: Byte) {
306-
currentElement += CborInt(value.toLong())
317+
currentElement += CborInt(value.toLong(), nextValueTags)
307318
}
308319

309320
override fun encodeChar(value: Char) {
310-
currentElement += CborInt(value.code.toLong())
321+
currentElement += CborInt(value.code.toLong(), nextValueTags)
311322
}
312323

313324
override fun encodeDouble(value: Double) {
314-
currentElement += CborDouble(value)
325+
currentElement += CborDouble(value, nextValueTags)
315326
}
316327

317328
override fun encodeFloat(value: Float) {
318-
currentElement += CborDouble(value.toDouble())
329+
currentElement += CborDouble(value.toDouble(), nextValueTags)
319330
}
320331

321332
override fun encodeInt(value: Int) {
322-
currentElement += CborInt(value.toLong())
333+
currentElement += CborInt(value.toLong(), nextValueTags)
323334
}
324335

325336
override fun encodeLong(value: Long) {
326-
currentElement += CborInt(value)
337+
currentElement += CborInt(value, nextValueTags)
327338
}
328339

329340
override fun encodeShort(value: Short) {
330-
currentElement += CborInt(value.toLong())
341+
currentElement += CborInt(value.toLong(), nextValueTags)
331342
}
332343

333344
override fun encodeString(value: String) {
334-
currentElement += CborString(value)
345+
currentElement += CborString(value, nextValueTags)
335346
}
336347

337348
override fun encodeByteString(byteArray: ByteArray) {
338-
currentElement += CborByteString(byteArray)
349+
currentElement += CborByteString(byteArray, nextValueTags)
339350
}
340351

341352
override fun encodeNull() {
342-
currentElement += CborNull()
353+
currentElement += if (isClass) CborMap(mapOf(), nextValueTags) /*NOT CBOR-COMPLIANT, KxS-proprietary behaviour*/
354+
else CborNull(nextValueTags)
343355
}
344356

345357
override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) {
346-
currentElement += CborString(enumDescriptor.getElementName(index))
358+
currentElement += CborString(enumDescriptor.getElementName(index), nextValueTags)
347359
}
348360

349361
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ class CborArrayTest {
116116
assertEquals(reference, cbor.decodeFromHexString(DoubleTaggedClassWithArray.serializer(), referenceHexString))
117117

118118
val struct = cbor.encodeToCbor(DoubleTaggedClassWithArray.serializer(), reference)
119+
val structFromHex = cbor.decodeFromHexString(CborElement.serializer(), referenceHexString)
120+
assertEquals(structFromHex, struct)
119121
assertEquals(reference, cbor.decodeFromCbor(DoubleTaggedClassWithArray.serializer(), struct))
120122
assertEquals(referenceHexString, cbor.encodeToHexString(CborElement.serializer(), struct))
121123
}

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

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -223,32 +223,43 @@ class CborTaggedTest {
223223

224224
@Test
225225
fun writeReadVerifyTaggedClass() {
226-
assertEquals(referenceHexString, Cbor {
226+
val cbor = Cbor {
227227
useDefiniteLengthEncoding = false
228228
encodeKeyTags = true
229229
encodeValueTags = true
230230
encodeObjectTags = true
231231
verifyKeyTags = true
232232
verifyValueTags = true
233233
verifyObjectTags = true
234-
}.encodeToHexString(DataWithTags.serializer(), reference))
234+
}
235+
assertEquals(referenceHexString, cbor.encodeToHexString(DataWithTags.serializer(), reference))
236+
val structFromHex = cbor.decodeFromHexString(CborElement.serializer(), referenceHexString)
237+
val struct = cbor.encodeToCbor(DataWithTags.serializer(), reference)
238+
assertEquals(struct, structFromHex)
239+
240+
assertEquals(reference, cbor.decodeFromCbor(DataWithTags.serializer(), struct))
241+
assertEquals(referenceHexString, cbor.encodeToHexString(CborElement.serializer(), struct))
242+
243+
val cborDef = Cbor {
244+
useDefiniteLengthEncoding = true
245+
encodeKeyTags = true
246+
encodeValueTags = true
247+
encodeObjectTags = true
248+
verifyKeyTags = true
249+
verifyValueTags = true
250+
verifyObjectTags = true
251+
}
235252
assertEquals(
236253
referenceHexStringDefLen,
237-
Cbor {
238-
useDefiniteLengthEncoding = true
239-
encodeKeyTags = true
240-
encodeValueTags = true
241-
encodeObjectTags = true
242-
verifyKeyTags = true
243-
verifyValueTags = true
244-
verifyObjectTags = true
245-
}.encodeToHexString(DataWithTags.serializer(), reference)
254+
cborDef.encodeToHexString(DataWithTags.serializer(), reference)
246255
)
256+
247257
assertEquals(reference, Cbor.CoseCompliant.decodeFromHexString(DataWithTags.serializer(), referenceHexString))
248258
assertEquals(
249259
reference,
250260
Cbor.CoseCompliant.decodeFromHexString(DataWithTags.serializer(), referenceHexStringDefLen)
251261
)
262+
252263
}
253264

254265
@Test

0 commit comments

Comments
 (0)