Skip to content

Commit aea3f4b

Browse files
committed
streamlining and cleanups
1 parent 10748f9 commit aea3f4b

File tree

2 files changed

+90
-62
lines changed

2 files changed

+90
-62
lines changed

formats/cbor/commonMain/src/kotlinx/serialization/cbor/CborDecoder.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public interface CborDecoder : Decoder {
3636
* Decodes the next element in the current input as [CborElement].
3737
* The type of the decoded element depends on the current state of the input and, when received
3838
* by [serializer][KSerializer] in its [KSerializer.serialize] method, the type of the token directly matches
39-
* the [kind][SerialDescriptor.kind].
39+
* the [kind][kotlinx.serialization.descriptors.SerialDescriptor.kind].
4040
*
4141
* This method is allowed to invoke only as the part of the whole deserialization process of the class,
4242
* calling this method after invoking [beginStructure] or any `decode*` method will lead to unspecified behaviour.

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

Lines changed: 89 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -554,111 +554,136 @@ private fun Iterable<ByteArray>.flatten(): ByteArray {
554554
return output
555555
}
556556

557+
/**
558+
* Iterator that keeps a reference to the current element and allows peeking at the next element.
559+
* Works for single elements (where current is directly set to the element) and for collections (where current
560+
* will be first set after `startMap` or `startArray`
561+
*/
562+
private class PeekingIterator<T : Any>(private val iter: ListIterator<T>) : Iterator<T> by iter {
563+
564+
lateinit var current: T
565+
private set
566+
567+
override fun next(): T = iter.next().also { current = it }
568+
569+
fun peek() = if (hasNext()) {
570+
val next = iter.next()
571+
iter.previous()
572+
next
573+
} else null
574+
575+
companion object {
576+
operator fun invoke(single: CborElement): PeekingIterator<CborElement> =
577+
PeekingIterator(listOf(single).listIterator()).also { it.next() }
578+
}
579+
}
580+
/** Maps are a bit special for nullability checks, so we need to check whether we're in a map or not*/
581+
private typealias CborLayer = Pair<Boolean, PeekingIterator<CborElement>>
582+
583+
private val CborLayer.isMap get() = first
557584

585+
/** current element reference inside a layer. If the layer is a primitive then the current element is the layer itself*/
586+
private val CborLayer.currentElement get() = second.current
587+
private fun CborLayer.peek() = second.peek()
588+
private fun CborLayer.hasNext() = second.hasNext()
589+
private fun CborLayer.nextElement() = second.next()
590+
591+
/**
592+
* CBOR parser that operates on [CborElement] instead of bytes. Closely mirrors the baheviour of [CborParser], so the
593+
* [CborDecoder] can remain largely unchanged.
594+
*/
558595
internal class StructuredCborParser(internal val element: CborElement, private val verifyObjectTags: Boolean) :
559596
CborParserInterface {
560597

598+
private var layer: CborLayer = false to PeekingIterator(element)
561599

562-
internal var currentElement = element
563-
private var listIterator: ListIterator<CborElement>? = null
564-
private var isMap = false
565-
private val isMapStack = ArrayDeque<Boolean>()
566-
private val layerStack = ArrayDeque<ListIterator<CborElement>?>()
567600

568-
override fun isNull(): Boolean {
569-
return if (isMap) {
570-
val isNull = listIterator!!.next() is CborNull
571-
listIterator!!.previous()
572-
isNull
573-
} else currentElement is CborNull
574-
}
601+
private val layerStack = ArrayDeque<CborLayer>()
575602

576-
override fun isEnd() = when {
577-
listIterator != null -> !listIterator!!.hasNext()
578-
else -> false
579-
}
603+
// map needs special treatment because keys and values are laid out as a list alternating between key and value to
604+
// mirror the byte-layout of a cbor map.
605+
override fun isNull() = if (layer.isMap) layer.peek() is CborNull else layer.currentElement is CborNull
606+
607+
override fun isEnd() = !layer.hasNext()
580608

581609
override fun end() {
582610
// Reset iterators when ending a structure
583-
isMap = isMapStack.removeLast()
584-
listIterator = layerStack.removeLast()
611+
layer = layerStack.removeLast()
585612
}
586613

587-
588614
override fun startArray(tags: ULongArray?): Int {
589615
processTags(tags)
590-
if (currentElement !is CborList) {
591-
throw CborDecodingException("Expected array, got ${currentElement::class.simpleName}")
616+
if (layer.currentElement !is CborList) {
617+
throw CborDecodingException("Expected array, got ${layer.currentElement::class.simpleName}")
592618
}
593-
isMapStack += isMap
594-
layerStack += listIterator
595-
isMap = false
596-
val list = currentElement as CborList
597-
listIterator = list.listIterator()
598-
return -1 //just let the iterator run out of elements
619+
layerStack += layer
620+
val list = layer.currentElement as CborList
621+
layer = false to PeekingIterator(list.listIterator())
622+
return list.size //we could just return -1 and let the current layer run out of elements to never run into inconsistencies
623+
// if we do keep it like this, any inconsistencies serve as a canary for implementation bugs
599624
}
600625

601626
override fun startMap(tags: ULongArray?): Int {
602627
processTags(tags)
603-
if (currentElement !is CborMap) {
604-
throw CborDecodingException("Expected map, got ${currentElement::class.simpleName}")
628+
if (layer.currentElement !is CborMap) {
629+
throw CborDecodingException("Expected map, got ${layer.currentElement::class.simpleName}")
605630
}
606-
layerStack += listIterator
607-
isMapStack += isMap
608-
isMap = true
631+
layerStack += layer
609632

610-
val map = currentElement as CborMap
611-
//zip key, value, key, value, ... pairs to mirror byte-layout of CBOR map
612-
listIterator = map.entries.flatMap { listOf(it.key, it.value) }.listIterator()
613-
return -1// just let the iterator run out of elements
633+
val map = layer.currentElement as CborMap
634+
// zip key, value, key, value, ... pairs to mirror byte-layout of CBOR map, so decoding this here works the same
635+
// as decoding from bytes
636+
layer = true to PeekingIterator(map.entries.flatMap { listOf(it.key, it.value) }.listIterator())
637+
return map.size//we could just return -1 and let the current layer run out of elements to never run into inconsistencies
638+
// if we do keep it like this, any inconsistencies serve as a canary for implementation bugs
614639
}
615640

616641
override fun nextNull(tags: ULongArray?): Nothing? {
617642
processTags(tags)
618-
if (currentElement !is CborNull) {
619-
throw CborDecodingException("Expected null, got ${currentElement::class.simpleName}")
643+
if (layer.currentElement !is CborNull) {
644+
throw CborDecodingException("Expected null, got ${layer.currentElement::class.simpleName}")
620645
}
621646
return null
622647
}
623648

624649
override fun nextBoolean(tags: ULongArray?): Boolean {
625650
processTags(tags)
626-
if (currentElement !is CborBoolean) {
627-
throw CborDecodingException("Expected boolean, got ${currentElement::class.simpleName}")
651+
if (layer.currentElement !is CborBoolean) {
652+
throw CborDecodingException("Expected boolean, got ${layer.currentElement::class.simpleName}")
628653
}
629-
return (currentElement as CborBoolean).value
654+
return (layer.currentElement as CborBoolean).value
630655
}
631656

632657
override fun nextNumber(tags: ULongArray?): Long {
633658
processTags(tags)
634-
return when (currentElement) {
635-
is CborPositiveInt -> (currentElement as CborPositiveInt).value.toLong()
636-
is CborNegativeInt -> (currentElement as CborNegativeInt).value
637-
else -> throw CborDecodingException("Expected number, got ${currentElement::class.simpleName}")
659+
return when (layer.currentElement) {
660+
is CborPositiveInt -> (layer.currentElement as CborPositiveInt).value.toLong()
661+
is CborNegativeInt -> (layer.currentElement as CborNegativeInt).value
662+
else -> throw CborDecodingException("Expected number, got ${layer.currentElement::class.simpleName}")
638663
}
639664
}
640665

641666
override fun nextString(tags: ULongArray?): String {
642667
processTags(tags)
643-
if (currentElement !is CborString) {
644-
throw CborDecodingException("Expected string, got ${currentElement::class.simpleName}")
668+
if (layer.currentElement !is CborString) {
669+
throw CborDecodingException("Expected string, got ${layer.currentElement::class.simpleName}")
645670
}
646-
return (currentElement as CborString).value
671+
return (layer.currentElement as CborString).value
647672
}
648673

649674
override fun nextByteString(tags: ULongArray?): ByteArray {
650675
processTags(tags)
651-
if (currentElement !is CborByteString) {
652-
throw CborDecodingException("Expected byte string, got ${currentElement::class.simpleName}")
676+
if (layer.currentElement !is CborByteString) {
677+
throw CborDecodingException("Expected byte string, got ${layer.currentElement::class.simpleName}")
653678
}
654-
return (currentElement as CborByteString).value
679+
return (layer.currentElement as CborByteString).value
655680
}
656681

657682
override fun nextDouble(tags: ULongArray?): Double {
658683
processTags(tags)
659-
return when (currentElement) {
660-
is CborDouble -> (currentElement as CborDouble).value
661-
else -> throw CborDecodingException("Expected double, got ${currentElement::class.simpleName}")
684+
return when (layer.currentElement) {
685+
is CborDouble -> (layer.currentElement as CborDouble).value
686+
else -> throw CborDecodingException("Expected double, got ${layer.currentElement::class.simpleName}")
662687
}
663688
}
664689

@@ -669,23 +694,27 @@ internal class StructuredCborParser(internal val element: CborElement, private v
669694
override fun nextTaggedStringOrNumber(): Triple<String?, Long?, ULongArray?> {
670695
val tags = processTags(null)
671696

672-
return when (val key = currentElement) {
697+
return when (val key = layer.currentElement) {
673698
is CborString -> Triple(key.value, null, tags)
674699
is CborPositiveInt -> Triple(null, key.value.toLong(), tags)
675700
is CborNegativeInt -> Triple(null, key.value, tags)
676701
else -> throw CborDecodingException("Expected string or number key, got ${key?.let { it::class.simpleName } ?: "null"}")
677702
}
678703
}
679704

705+
/**
706+
* Verify the current element's object tags and advance to the next element if inside a list/map.
707+
* The reason this method mixes two behaviours is that decoding a primitive is invoked on a single element.
708+
* `decodeElementIndex`, etc. is invoked on an iterable and there are key tags and value tags
709+
*/
680710
private fun processTags(tags: ULongArray?): ULongArray? {
681711

682-
// If we're in a list, advance to the next element
683-
if (listIterator != null && listIterator!!.hasNext()) {
684-
currentElement = listIterator!!.next()
685-
}
712+
// If we're in a list/map, advance to the next element
713+
if (layer.hasNext()) layer.nextElement()
714+
// if we're at a primitive, we only process tags
686715

687716
// Store collected tags for verification
688-
val collectedTags = if (currentElement.tags.isEmpty()) null else currentElement.tags
717+
val collectedTags = if (layer.currentElement.tags.isEmpty()) null else layer.currentElement.tags
689718

690719
// Verify tags if needed
691720
if (verifyObjectTags) {
@@ -707,7 +736,6 @@ internal class StructuredCborParser(internal val element: CborElement, private v
707736

708737
override fun skipElement(tags: ULongArray?) {
709738
// Process tags but don't do anything with the element
710-
//TODO check for maps
711739
processTags(tags)
712740
}
713741
}

0 commit comments

Comments
 (0)