Skip to content

Commit e3c207b

Browse files
committed
fix object tags
1 parent e840129 commit e3c207b

File tree

3 files changed

+68
-77
lines changed

3 files changed

+68
-77
lines changed

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

Lines changed: 49 additions & 52 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.layer.currentElement
21+
is StructuredCborParser -> parser.layer.current
2222
}
2323

2424

@@ -119,7 +119,9 @@ internal open class CborReader(override val cbor: Cbor, protected val parser: Cb
119119
@Suppress("UNCHECKED_CAST")
120120
return if (deserializer is CborSerializer) {
121121
val tags = parser.processTags(tags)
122-
decodeCborElement().also { /*this is a NOOP for structured parser but not from bytes */it.tags = tags ?: ulongArrayOf() } as T
122+
decodeCborElement().also { /*this is a NOOP for structured parser but not from bytes */it.tags =
123+
tags ?: ulongArrayOf()
124+
} as T
123125
} else if ((decodeByteArrayAsByteString || cbor.configuration.alwaysUseByteString)
124126
&& deserializer.descriptor == ByteArraySerializer().descriptor
125127
) {
@@ -563,12 +565,15 @@ private fun Iterable<ByteArray>.flatten(): ByteArray {
563565
* Works for single elements (where current is directly set to the element) and for collections (where current
564566
* will be first set after `startMap` or `startArray`
565567
*/
566-
internal class PeekingIterator<T : Any>(private val iter: ListIterator<T>) : Iterator<T> by iter {
568+
internal class PeekingIterator private constructor(
569+
internal val isStructure: Boolean,
570+
private val iter: ListIterator<CborElement>
571+
) : Iterator<CborElement> by iter {
567572

568-
lateinit var current: T
573+
lateinit var current: CborElement
569574
private set
570575

571-
override fun next(): T = iter.next().also { current = it }
576+
override fun next(): CborElement = iter.next().also { current = it }
572577

573578
fun peek() = if (hasNext()) {
574579
val next = iter.next()
@@ -577,21 +582,13 @@ internal class PeekingIterator<T : Any>(private val iter: ListIterator<T>) : Ite
577582
} else null
578583

579584
companion object {
580-
operator fun invoke(single: CborElement): PeekingIterator<CborElement> =
581-
PeekingIterator(listOf(single).listIterator()).also { it.next() }
585+
operator fun invoke(single: CborElement): PeekingIterator =
586+
PeekingIterator(false, listOf(single).listIterator()).also { it.next() }
587+
588+
operator fun invoke(iter: ListIterator<CborElement>): PeekingIterator =
589+
PeekingIterator(true, iter)
582590
}
583591
}
584-
/** Maps are a bit special for nullability checks, so we need to check whether we're in a map or not*/
585-
private typealias CborLayer = Pair<Boolean, PeekingIterator<CborElement>>
586-
587-
//TODO get rid of this!
588-
private val CborLayer.isMap get() = first
589-
590-
/** current element reference inside a layer. If the layer is a primitive then the current element is the layer itself*/
591-
private val CborLayer.currentElement get() = second.current
592-
private fun CborLayer.peek() = second.peek()
593-
private fun CborLayer.hasNext() = second.hasNext()
594-
private fun CborLayer.nextElement() = second.next()
595592

596593
/**
597594
* CBOR parser that operates on [CborElement] instead of bytes. Closely mirrors the behaviour of [CborParser], so the
@@ -600,20 +597,20 @@ private fun CborLayer.nextElement() = second.next()
600597
internal class StructuredCborParser(internal val element: CborElement, private val verifyObjectTags: Boolean) :
601598
CborParserInterface {
602599

603-
internal var layer: CborLayer = false to PeekingIterator(element)
600+
internal var layer: PeekingIterator = PeekingIterator(element)
604601
private set
605602

606603

607-
private val layerStack = ArrayDeque<CborLayer>()
604+
private val layerStack = ArrayDeque<PeekingIterator>()
608605

609606
// map needs special treatment because keys and values are laid out as a list alternating between key and value to
610607
// mirror the byte-layout of a cbor map.
611608
override fun isNull() =
612-
if (layer.isMap) layer.peek().let {
609+
if (layer.isStructure) layer.peek().let {
613610
it is CborNull ||
614611
/*THIS IS NOT CBOR-COMPLIANT but KxS-proprietary handling of nullable classes*/
615612
(it is CborMap && it.isEmpty())
616-
} else layer.currentElement is CborNull
613+
} else layer.current is CborNull
617614

618615
override fun isEnd() = !layer.hasNext()
619616

@@ -624,80 +621,80 @@ internal class StructuredCborParser(internal val element: CborElement, private v
624621

625622
override fun startArray(tags: ULongArray?): Int {
626623
processTags(tags)
627-
if (layer.currentElement !is CborList) {
628-
throw CborDecodingException("Expected array, got ${layer.currentElement::class.simpleName}")
624+
if (layer.current !is CborList) {
625+
throw CborDecodingException("Expected array, got ${layer.current::class.simpleName}")
629626
}
630627
layerStack += layer
631-
val list = layer.currentElement as CborList
632-
layer = true to PeekingIterator(list.listIterator())
628+
val list = layer.current as CborList
629+
layer = PeekingIterator(list.listIterator())
633630
return list.size //we could just return -1 and let the current layer run out of elements to never run into inconsistencies
634631
// if we do keep it like this, any inconsistencies serve as a canary for implementation bugs
635632
}
636633

637634
override fun startMap(tags: ULongArray?): Int {
638635
processTags(tags)
639-
if (layer.currentElement !is CborMap) {
640-
throw CborDecodingException("Expected map, got ${layer.currentElement::class.simpleName}")
636+
if (layer.current !is CborMap) {
637+
throw CborDecodingException("Expected map, got ${layer.current::class.simpleName}")
641638
}
642639
layerStack += layer
643640

644-
val map = layer.currentElement as CborMap
641+
val map = layer.current as CborMap
645642
// zip key, value, key, value, ... pairs to mirror byte-layout of CBOR map, so decoding this here works the same
646643
// as decoding from bytes
647-
layer = true to PeekingIterator(map.entries.flatMap { listOf(it.key, it.value) }.listIterator())
644+
layer = PeekingIterator(map.entries.flatMap { listOf(it.key, it.value) }.listIterator())
648645
return map.size//we could just return -1 and let the current layer run out of elements to never run into inconsistencies
649646
// if we do keep it like this, any inconsistencies serve as a canary for implementation bugs
650647
}
651648

652649
override fun nextNull(tags: ULongArray?): Nothing? {
653650
processTags(tags)
654-
if (layer.currentElement !is CborNull) {
651+
if (layer.current !is CborNull) {
655652
/* THIS IS NOT CBOR-COMPLIANT but KxS-proprietary handling of nullable classes*/
656-
if (layer.currentElement is CborMap && (layer.currentElement as CborMap).isEmpty())
653+
if (layer.current is CborMap && (layer.current as CborMap).isEmpty())
657654
return null
658-
throw CborDecodingException("Expected null, got ${layer.currentElement::class.simpleName}")
655+
throw CborDecodingException("Expected null, got ${layer.current::class.simpleName}")
659656
}
660657
return null
661658
}
662659

663660
override fun nextBoolean(tags: ULongArray?): Boolean {
664661
processTags(tags)
665-
if (layer.currentElement !is CborBoolean) {
666-
throw CborDecodingException("Expected boolean, got ${layer.currentElement::class.simpleName}")
662+
if (layer.current !is CborBoolean) {
663+
throw CborDecodingException("Expected boolean, got ${layer.current::class.simpleName}")
667664
}
668-
return (layer.currentElement as CborBoolean).value
665+
return (layer.current as CborBoolean).value
669666
}
670667

671668
override fun nextNumber(tags: ULongArray?): Long {
672669
processTags(tags)
673-
return when (layer.currentElement) {
674-
is CborPositiveInt -> (layer.currentElement as CborPositiveInt).value.toLong()
675-
is CborNegativeInt -> (layer.currentElement as CborNegativeInt).value
676-
else -> throw CborDecodingException("Expected number, got ${layer.currentElement::class.simpleName}")
670+
return when (layer.current) {
671+
is CborPositiveInt -> (layer.current as CborPositiveInt).value.toLong()
672+
is CborNegativeInt -> (layer.current as CborNegativeInt).value
673+
else -> throw CborDecodingException("Expected number, got ${layer.current::class.simpleName}")
677674
}
678675
}
679676

680677
override fun nextString(tags: ULongArray?): String {
681678
processTags(tags)
682-
if (layer.currentElement !is CborString) {
683-
throw CborDecodingException("Expected string, got ${layer.currentElement::class.simpleName}")
679+
if (layer.current !is CborString) {
680+
throw CborDecodingException("Expected string, got ${layer.current::class.simpleName}")
684681
}
685-
return (layer.currentElement as CborString).value
682+
return (layer.current as CborString).value
686683
}
687684

688685
override fun nextByteString(tags: ULongArray?): ByteArray {
689686
processTags(tags)
690-
if (layer.currentElement !is CborByteString) {
691-
throw CborDecodingException("Expected byte string, got ${layer.currentElement::class.simpleName}")
687+
if (layer.current !is CborByteString) {
688+
throw CborDecodingException("Expected byte string, got ${layer.current::class.simpleName}")
692689
}
693-
return (layer.currentElement as CborByteString).value
690+
return (layer.current as CborByteString).value
694691
}
695692

696693
override fun nextDouble(tags: ULongArray?): Double {
697694
processTags(tags)
698-
return when (layer.currentElement) {
699-
is CborDouble -> (layer.currentElement as CborDouble).value
700-
else -> throw CborDecodingException("Expected double, got ${layer.currentElement::class.simpleName}")
695+
return when (layer.current) {
696+
is CborDouble -> (layer.current as CborDouble).value
697+
else -> throw CborDecodingException("Expected double, got ${layer.current::class.simpleName}")
701698
}
702699
}
703700

@@ -708,7 +705,7 @@ internal class StructuredCborParser(internal val element: CborElement, private v
708705
override fun nextTaggedStringOrNumber(): Triple<String?, Long?, ULongArray?> {
709706
val tags = processTags(null)
710707

711-
return when (val key = layer.currentElement) {
708+
return when (val key = layer.current) {
712709
is CborString -> Triple(key.value, null, tags)
713710
is CborPositiveInt -> Triple(null, key.value.toLong(), tags)
714711
is CborNegativeInt -> Triple(null, key.value, tags)
@@ -724,11 +721,11 @@ internal class StructuredCborParser(internal val element: CborElement, private v
724721
override fun processTags(tags: ULongArray?): ULongArray? {
725722

726723
// If we're in a list/map, advance to the next element
727-
if (layer.hasNext()) layer.nextElement()
724+
if (layer.hasNext()) layer.next()
728725
// if we're at a primitive, we only process tags
729726

730727
// Store collected tags for verification
731-
val collectedTags = if (layer.currentElement.tags.isEmpty()) null else layer.currentElement.tags
728+
val collectedTags = if (layer.current.tags.isEmpty()) null else layer.current.tags
732729

733730
// Verify tags if needed
734731
if (verifyObjectTags) {

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,9 @@ internal class StructuredCborWriter(cbor: Cbor) : CborWriter(cbor) {
249249
fun finalize() = currentElement!!.finalize()
250250

251251
override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder {
252-
val tags = nextValueTags + (descriptor.getObjectTags() ?: ulongArrayOf())
252+
val tags = nextValueTags +
253+
if (cbor.configuration.encodeObjectTags) descriptor.getObjectTags() ?: ulongArrayOf()
254+
else ulongArrayOf()
253255
val element = if (descriptor.hasArrayTag()) {
254256
CborContainer.List(tags)
255257
} else {

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

Lines changed: 16 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -670,8 +670,10 @@ class CborTaggedTest {
670670
)
671671
(cborNoVerifyObjTags to referenceHexString).let { (cbor, hexString) ->
672672
val struct = cbor.encodeToCbor(reference)
673-
assertEquals(hexString, cbor.encodeToHexString(CborElement.serializer(), struct))
674-
assertEquals(cbor.decodeFromHexString<CborElement>(hexString), struct)
673+
// NEQ: the ref string has object tags, but here we don't encode them
674+
assertNotEquals(hexString, cbor.encodeToHexString(CborElement.serializer(), struct))
675+
// NEW, the hex string has the tags, so they are decoded, but the struct, created without object tags does not
676+
assertNotEquals(cbor.decodeFromHexString<CborElement>(hexString), struct)
675677
assertEquals(reference, cbor.decodeFromCbor(ClassAsTagged.serializer(), struct))
676678
}
677679

@@ -682,9 +684,8 @@ class CborTaggedTest {
682684
)
683685
(cborNoEncodeObjTags to untaggedHexString).let { (cbor, hexString) ->
684686
val struct = cbor.encodeToCbor(reference)
685-
//cbor is encoding tags from the reference (i.e. based on tags picked up by the serializer, so this must fail)
686-
assertNotEquals(hexString, cbor.encodeToHexString(CborElement.serializer(), struct))
687-
assertNotEquals(cbor.decodeFromHexString<CborElement>(hexString), struct)
687+
assertEquals(hexString, cbor.encodeToHexString(CborElement.serializer(), struct))
688+
assertEquals(cbor.decodeFromHexString<CborElement>(hexString), struct)
688689
assertEquals(reference, cbor.decodeFromCbor(ClassAsTagged.serializer(), struct))
689690
}
690691

@@ -695,9 +696,8 @@ class CborTaggedTest {
695696
)
696697
(cborNoVerifyObjTags to untaggedHexString).let { (cbor, hexString) ->
697698
val struct = cbor.encodeToCbor(reference)
698-
//cbor is encoding tags from the reference (i.e. based on tags picked up by the serializer, so this must fail)
699-
assertNotEquals(hexString, cbor.encodeToHexString(CborElement.serializer(), struct))
700-
assertNotEquals(cbor.decodeFromHexString<CborElement>(hexString), struct)
699+
assertEquals(hexString, cbor.encodeToHexString(CborElement.serializer(), struct))
700+
assertEquals(cbor.decodeFromHexString<CborElement>(hexString), struct)
701701
assertEquals(reference, cbor.decodeFromCbor(ClassAsTagged.serializer(), struct))
702702
}
703703

@@ -814,8 +814,11 @@ class CborTaggedTest {
814814
}.message
815815
)
816816
assertEquals(
817-
"More tags found than the 1 tags specified",
818-
assertFailsWith(CborDecodingException::class, message = "More tags found than the 1 tags specified") {
817+
"CBOR tags [19, 20] do not match expected tags [19]",
818+
assertFailsWith(
819+
CborDecodingException::class,
820+
message = "CBOR tags [19, 20] do not match expected tags [19]"
821+
) {
819822
cbor.decodeFromCbor(
820823
NestedTagged.serializer(),
821824
cbor.decodeFromHexString(CborElement.serializer(), referenceHexStringWithBogusTag)
@@ -874,7 +877,7 @@ class CborTaggedTest {
874877
encodeValueTags = true
875878
verifyKeyTags = true
876879
verifyValueTags = true
877-
verifyObjectTags = true
880+
verifyObjectTags = false
878881
encodeObjectTags = false
879882
}
880883
assertEquals(
@@ -894,7 +897,8 @@ class CborTaggedTest {
894897
)
895898
(cborNoVerifying to untaggedHexString).let { (cbor, hex) ->
896899
val struct = cbor.encodeToCbor(reference)
897-
assertEquals(struct, cbor.decodeFromHexString<CborElement>(hex))
900+
//NEQ: decoding from an untagged string means no tags coming in while encoding path above creates those tags
901+
assertNotEquals(struct, cbor.decodeFromHexString<CborElement>(hex))
898902
assertEquals(reference, cbor.decodeFromCbor(struct))
899903
}
900904

@@ -921,18 +925,6 @@ class CborTaggedTest {
921925
)
922926
}.message ?: "", "do not start with specified tags"
923927
)
924-
assertContains(
925-
assertFailsWith(CborDecodingException::class) {
926-
cborNoVerifying.decodeFromCbor(
927-
NestedTagged.serializer(), cborNoVerifying.decodeFromHexString(
928-
CborElement.serializer(),
929-
superfluousWrongTaggedTagged
930-
)
931-
)
932-
}.message ?: "", "do not start with specified tags"
933-
)
934-
935-
936928
}
937929

938930
@ObjectTags(1337uL)

0 commit comments

Comments
 (0)