diff --git a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/cbor/CborBaseLine.kt b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/cbor/CborBaseLine.kt
index 8b71d92b7f..0b05d8f50e 100644
--- a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/cbor/CborBaseLine.kt
+++ b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/cbor/CborBaseLine.kt
@@ -52,6 +52,7 @@ open class CborBaseline {
}
val baseBytes = cbor.encodeToByteArray(KTestOuterMessage.serializer(), baseMessage)
+ val baseStruct = cbor.encodeToCborElement(KTestOuterMessage.serializer(), baseMessage)
@Benchmark
fun toBytes() = cbor.encodeToByteArray(KTestOuterMessage.serializer(), baseMessage)
@@ -59,4 +60,17 @@ open class CborBaseline {
@Benchmark
fun fromBytes() = cbor.decodeFromByteArray(KTestOuterMessage.serializer(), baseBytes)
+
+ @Benchmark
+ fun structToBytes() = cbor.encodeToByteArray(CborElement.serializer(), baseStruct)
+
+ @Benchmark
+ fun structFromBytes() = cbor.decodeFromByteArray(CborElement.serializer(), baseBytes)
+
+ @Benchmark
+ fun fromStruct() = cbor.decodeFromCborElement(KTestOuterMessage.serializer(), baseStruct)
+
+ @Benchmark
+ fun toStruct() = cbor.encodeToCborElement(KTestOuterMessage.serializer(), baseMessage)
+
}
diff --git a/docs/formats.md b/docs/formats.md
index 307601234d..da70e76264 100644
--- a/docs/formats.md
+++ b/docs/formats.md
@@ -17,6 +17,11 @@ stable, these are currently experimental features of Kotlin Serialization.
* [Tags and Labels](#tags-and-labels)
* [Arrays](#arrays)
* [Custom CBOR-specific Serializers](#custom-cbor-specific-serializers)
+ * [CBOR Elements](#cbor-elements)
+ * [Encoding from/to `CborElement`](#encoding-fromto-cborelement)
+ * [Tagging `CborElement`s](#tagging-cborelements)
+ * [Caution](#caution)
+ * [Types of CBOR Elements](#types-of-cbor-elements)
* [ProtoBuf (experimental)](#protobuf-experimental)
* [Field numbers](#field-numbers)
* [Integer types](#integer-types)
@@ -308,13 +313,125 @@ When annotated with `@CborArray`, serialization of the same object will produce
```
This may be used to encode COSE structures, see [RFC 9052 2. Basic COSE Structure](https://www.rfc-editor.org/rfc/rfc9052#section-2).
-
### Custom CBOR-specific Serializers
Cbor encoders and decoders implement the interfaces [CborEncoder](CborEncoder.kt) and [CborDecoder](CborDecoder.kt), respectively.
These interfaces contain a single property, `cbor`, exposing the current CBOR serialization configuration.
This enables custom cbor-specific serializers to reuse the current `Cbor` instance to produce embedded byte arrays or
react to configuration settings such as `preferCborLabelsOverNames` or `useDefiniteLengthEncoding`, for example.
+
+### CBOR Elements
+
+Aside from direct conversions between bytearray and CBOR objects, Kotlin serialization offers APIs that allow
+other ways of working with CBOR in the code. For example, you might need to tweak the data before it can parse
+or otherwise work with such unstructured data that it does not readily fit into the typesafe world of Kotlin
+serialization.
+
+The main concept in this part of the library is [CborElement]. Read on to learn what you can do with it.
+
+#### Encoding from/to `CborElement`
+
+Bytes can be decoded into an instance of `CborElement` with the [Cbor.decodeFromByteArray] function by either manually
+specifying [CborElement.serializer()] or specifying [CborElement] as generic type parameter.
+It is also possible to encode arbitrary serializable structures to a `CborElement` through [Cbor.encodeToCborElement].
+
+Since these operations use the same code paths as regular serialization (but with specialized serializers), the config flags
+behave as expected:
+
+```kotlin
+fun main() {
+ val element: CborElement = Cbor.decodeFromHexString("a165627974657343666f6f")
+ println(element)
+}
+```
+
+The above snippet will print the following diagnostic notation
+
+```text
+CborMap(tags=[], content={CborString(tags=[], value=bytes)=CborByteString(tags=[], value=h'666f6f)})
+```
+
+#### Tagging `CborElement`s
+
+Every CborElement—whether it is used as a property, a value inside a collection, or even a complex key inside a map
+(which is perfectly legal in CBOR)—supports tags. Tags can be specified by passing them s varargs parameters upon
+CborElement creation.
+For example, take following structure (represented in diagnostic notation):
+
+
+
+```hexdump
+bf # map(*)
+ 61 # text(1)
+ 61 # "a"
+ cc # tag(12)
+ 1a 0fffffff # unsigned(268,435,455)
+ d8 22 # base64 encoded text, tag(34)
+ 61 # text(1)
+ 62 # "b"
+ # invalid length at 0 for base64
+ 20 # negative(-1)
+ d8 38 # tag(56)
+ 61 # text(1)
+ 63 # "c"
+ d8 4e # typed array of i32, little endian, twos-complement, tag(78)
+ 42 # bytes(2)
+ cafe # "\xca\xfe"
+ # invalid data length for typed array
+ 61 # text(1)
+ 64 # "d"
+ d8 5a # tag(90)
+ cc # tag(12)
+ 6b # text(11)
+ 48656c6c6f20576f726c64 # "Hello World"
+ ff # break
+```
+
+Decoding it results in the following CborElement (shown in manually formatted diagnostic notation):
+
+```
+CborMap(tags=[], content={
+ CborString(tags=[], value=a) = CborPositiveInt( tags=[12], value=268435455),
+ CborString(tags=[34], value=b) = CborNegativeInt( tags=[], value=-1),
+ CborString(tags=[56], value=c) = CborByteString( tags=[78], value=h'cafe),
+ CborString(tags=[], value=d) = CborString( tags=[90, 12], value=Hello World)
+})
+```
+
+##### Caution
+
+Tags are properties of `CborElements`, and it is possible to mixing arbitrary serializable values with `CborElement`s that
+contain tags inside a serializable structure. It is also possible to annotate any [CborElement] property
+of a generic serializable class with `@ValueTags`.
+**This can lead to asymmetric behavior when serializing and deserializing such structures!**
+
+#### Types of CBOR Elements
+
+A [CborElement] class has three direct subtypes, closely following CBOR grammar:
+
+* [CborPrimitive] represents primitive CBOR elements, such as string, integer, float boolean, and null.
+ CBOR byte strings are also treated as primitives
+ Each primitive has a [value][CborPrimitive.value]. Depending on the concrete type of the primitive, it maps
+ to corresponding Kotlin Types such as `String`, `Int`, `Double`, etc.
+ Note that Cbor discriminates between positive ("unsigned") and negative ("signed") integers!
+ `CborPrimitive` is itself an umbrella type (a sealed class) for the following concrete primitives:
+ * [CborNull] mapping to a Kotlin `null`
+ * [CborBoolean] mapping to a Kotlin `Boolean`
+ * [CborInt] which is an umbrella type (a sealed class) itself for the following concrete types
+ (it is still possible to instantiate it as the `invoke` operator on its companion is overridden accordingly):
+ * [CborPositiveInt] represents all `Long` numbers `≥0`
+ * [CborNegativeInt] represents all `Long` numbers `<0`
+ * [CborString] maps to a Kotlin `String`
+ * [CborFloat] maps to Kotlin `Double`
+ * [CborByteString] maps to a Kotlin `ByteArray` and is used to encode them as CBOR byte string (in contrast to a list
+ of individual bytes)
+
+* [CborList] represents a CBOR array. It is a Kotlin [List] of `CborElement` items.
+
+* [CborMap] represents a CBOR map/object. It is a Kotlin [Map] from `CborElement` keys to `CborElement` values.
+ This is typically the result of serializing an arbitrary
+
+
## ProtoBuf (experimental)
[Protocol Buffers](https://developers.google.com/protocol-buffers) is a language-neutral binary format that normally
@@ -1673,5 +1790,19 @@ This chapter concludes [Kotlin Serialization Guide](serialization-guide.md).
[Cbor.decodeFromByteArray]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor/decode-from-byte-array.html
[CborBuilder.ignoreUnknownKeys]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-builder/ignore-unknown-keys.html
[ByteString]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-byte-string/index.html
+[CborElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-element/index.html
+[Cbor.encodeToCborElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/encode-to-cbor-element.html
+[CborPrimitive]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-primitive/index.html
+[CborPrimitive.value]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-primitive/value.html
+[CborNull]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-null/index.html
+[CborBoolean]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-boolean/index.html
+[CborInt]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-int/index.html
+[CborPositiveInt]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-positive-int/index.html
+[CborNegativeInt]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-negative-int/index.html
+[CborString]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-string/index.html
+[CborFloat]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-float/index.html
+[CborByteString]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-byte-string/index.html
+[CborList]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-list/index.html
+[CborMap]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-map/index.html
diff --git a/docs/serialization-guide.md b/docs/serialization-guide.md
index ce7aeef343..59a3dc75f5 100644
--- a/docs/serialization-guide.md
+++ b/docs/serialization-guide.md
@@ -154,6 +154,11 @@ Once the project is set up, we can start serializing some classes.
* [Tags and Labels](formats.md#tags-and-labels)
* [Arrays](formats.md#arrays)
* [Custom CBOR-specific Serializers](formats.md#custom-cbor-specific-serializers)
+ * [CBOR Elements](formats.md#cbor-elements)
+ * [Encoding from/to `CborElement`](formats.md#encoding-fromto-cborelement)
+ * [Tagging `CborElement`s](formats.md#tagging-cborelements)
+ * [Caution](formats.md#caution)
+ * [Types of CBOR Elements](formats.md#types-of-cbor-elements)
* [ProtoBuf (experimental)](formats.md#protobuf-experimental)
* [Field numbers](formats.md#field-numbers)
* [Integer types](formats.md#integer-types)
diff --git a/formats/cbor/api/kotlinx-serialization-cbor.api b/formats/cbor/api/kotlinx-serialization-cbor.api
index e1e37801f6..4434fcf43f 100644
--- a/formats/cbor/api/kotlinx-serialization-cbor.api
+++ b/formats/cbor/api/kotlinx-serialization-cbor.api
@@ -9,7 +9,9 @@ public abstract class kotlinx/serialization/cbor/Cbor : kotlinx/serialization/Bi
public static final field Default Lkotlinx/serialization/cbor/Cbor$Default;
public synthetic fun (Lkotlinx/serialization/cbor/CborConfiguration;Lkotlinx/serialization/modules/SerializersModule;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun decodeFromByteArray (Lkotlinx/serialization/DeserializationStrategy;[B)Ljava/lang/Object;
+ public final fun decodeFromCborElement (Lkotlinx/serialization/DeserializationStrategy;Lkotlinx/serialization/cbor/CborElement;)Ljava/lang/Object;
public fun encodeToByteArray (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)[B
+ public final fun encodeToCborElement (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)Lkotlinx/serialization/cbor/CborElement;
public final fun getConfiguration ()Lkotlinx/serialization/cbor/CborConfiguration;
public fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule;
}
@@ -25,6 +27,15 @@ public final synthetic class kotlinx/serialization/cbor/CborArray$Impl : kotlinx
public fun ()V
}
+public final class kotlinx/serialization/cbor/CborBoolean : kotlinx/serialization/cbor/CborPrimitive {
+ public static final field Companion Lkotlinx/serialization/cbor/CborBoolean$Companion;
+ public synthetic fun (Z[JLkotlin/jvm/internal/DefaultConstructorMarker;)V
+}
+
+public final class kotlinx/serialization/cbor/CborBoolean$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
public final class kotlinx/serialization/cbor/CborBuilder {
public final fun getAlwaysUseByteString ()Z
public final fun getEncodeDefaults ()Z
@@ -52,6 +63,18 @@ public final class kotlinx/serialization/cbor/CborBuilder {
public final fun setVerifyValueTags (Z)V
}
+public final class kotlinx/serialization/cbor/CborByteString : kotlinx/serialization/cbor/CborPrimitive {
+ public static final field Companion Lkotlinx/serialization/cbor/CborByteString$Companion;
+ public synthetic fun ([B[JLkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public fun equals (Ljava/lang/Object;)Z
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class kotlinx/serialization/cbor/CborByteString$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
public final class kotlinx/serialization/cbor/CborConfiguration {
public final fun getAlwaysUseByteString ()Z
public final fun getEncodeDefaults ()Z
@@ -68,6 +91,7 @@ public final class kotlinx/serialization/cbor/CborConfiguration {
}
public abstract interface class kotlinx/serialization/cbor/CborDecoder : kotlinx/serialization/encoding/Decoder {
+ public abstract fun decodeCborElement ()Lkotlinx/serialization/cbor/CborElement;
public abstract fun getCbor ()Lkotlinx/serialization/cbor/Cbor;
}
@@ -76,6 +100,19 @@ public final class kotlinx/serialization/cbor/CborDecoder$DefaultImpls {
public static fun decodeSerializableValue (Lkotlinx/serialization/cbor/CborDecoder;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object;
}
+public abstract class kotlinx/serialization/cbor/CborElement {
+ public static final field Companion Lkotlinx/serialization/cbor/CborElement$Companion;
+ public synthetic fun ([JILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public synthetic fun ([JLkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getTags-Y2RjT0g ()[J
+ public fun hashCode ()I
+}
+
+public final class kotlinx/serialization/cbor/CborElement$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
public abstract interface class kotlinx/serialization/cbor/CborEncoder : kotlinx/serialization/encoding/Encoder {
public abstract fun getCbor ()Lkotlinx/serialization/cbor/Cbor;
}
@@ -87,6 +124,27 @@ public final class kotlinx/serialization/cbor/CborEncoder$DefaultImpls {
public static fun encodeSerializableValue (Lkotlinx/serialization/cbor/CborEncoder;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V
}
+public final class kotlinx/serialization/cbor/CborFloat : kotlinx/serialization/cbor/CborPrimitive {
+ public static final field Companion Lkotlinx/serialization/cbor/CborFloat$Companion;
+ public synthetic fun (D[JLkotlin/jvm/internal/DefaultConstructorMarker;)V
+}
+
+public final class kotlinx/serialization/cbor/CborFloat$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public abstract class kotlinx/serialization/cbor/CborInt : kotlinx/serialization/cbor/CborPrimitive {
+ public static final field Companion Lkotlinx/serialization/cbor/CborInt$Companion;
+ public synthetic fun ([JLjava/lang/Object;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public synthetic fun ([JLjava/lang/Object;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+}
+
+public final class kotlinx/serialization/cbor/CborInt$Companion {
+ public final fun invoke-SIFponk (J[J)Lkotlinx/serialization/cbor/CborInt;
+ public final fun invoke-ahITK_k (J[J)Lkotlinx/serialization/cbor/CborInt;
+ public final fun serializer (Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer;
+}
+
public final class kotlinx/serialization/cbor/CborKt {
public static final fun Cbor (Lkotlinx/serialization/cbor/Cbor;Lkotlin/jvm/functions/Function1;)Lkotlinx/serialization/cbor/Cbor;
public static synthetic fun Cbor$default (Lkotlinx/serialization/cbor/Cbor;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlinx/serialization/cbor/Cbor;
@@ -101,6 +159,152 @@ public final synthetic class kotlinx/serialization/cbor/CborLabel$Impl : kotlinx
public final synthetic fun label ()J
}
+public final class kotlinx/serialization/cbor/CborList : kotlinx/serialization/cbor/CborElement, java/util/List, kotlin/jvm/internal/markers/KMappedMarker {
+ public static final field Companion Lkotlinx/serialization/cbor/CborList$Companion;
+ public synthetic fun (Ljava/util/List;[JLkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public synthetic fun add (ILjava/lang/Object;)V
+ public fun add (ILkotlinx/serialization/cbor/CborElement;)V
+ public synthetic fun add (Ljava/lang/Object;)Z
+ public fun add (Lkotlinx/serialization/cbor/CborElement;)Z
+ public fun addAll (ILjava/util/Collection;)Z
+ public fun addAll (Ljava/util/Collection;)Z
+ public fun clear ()V
+ public final fun contains (Ljava/lang/Object;)Z
+ public fun contains (Lkotlinx/serialization/cbor/CborElement;)Z
+ public fun containsAll (Ljava/util/Collection;)Z
+ public fun equals (Ljava/lang/Object;)Z
+ public synthetic fun get (I)Ljava/lang/Object;
+ public fun get (I)Lkotlinx/serialization/cbor/CborElement;
+ public fun getSize ()I
+ public fun hashCode ()I
+ public final fun indexOf (Ljava/lang/Object;)I
+ public fun indexOf (Lkotlinx/serialization/cbor/CborElement;)I
+ public fun isEmpty ()Z
+ public fun iterator ()Ljava/util/Iterator;
+ public final fun lastIndexOf (Ljava/lang/Object;)I
+ public fun lastIndexOf (Lkotlinx/serialization/cbor/CborElement;)I
+ public fun listIterator ()Ljava/util/ListIterator;
+ public fun listIterator (I)Ljava/util/ListIterator;
+ public synthetic fun remove (I)Ljava/lang/Object;
+ public fun remove (I)Lkotlinx/serialization/cbor/CborElement;
+ public fun remove (Ljava/lang/Object;)Z
+ public fun removeAll (Ljava/util/Collection;)Z
+ public fun replaceAll (Ljava/util/function/UnaryOperator;)V
+ public fun retainAll (Ljava/util/Collection;)Z
+ public synthetic fun set (ILjava/lang/Object;)Ljava/lang/Object;
+ public fun set (ILkotlinx/serialization/cbor/CborElement;)Lkotlinx/serialization/cbor/CborElement;
+ public final fun size ()I
+ public fun sort (Ljava/util/Comparator;)V
+ public fun subList (II)Ljava/util/List;
+ public fun toArray ()[Ljava/lang/Object;
+ public fun toArray ([Ljava/lang/Object;)[Ljava/lang/Object;
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class kotlinx/serialization/cbor/CborList$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class kotlinx/serialization/cbor/CborMap : kotlinx/serialization/cbor/CborElement, java/util/Map, kotlin/jvm/internal/markers/KMappedMarker {
+ public static final field Companion Lkotlinx/serialization/cbor/CborMap$Companion;
+ public synthetic fun (Ljava/util/Map;[JLkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public fun clear ()V
+ public synthetic fun compute (Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object;
+ public fun compute (Lkotlinx/serialization/cbor/CborElement;Ljava/util/function/BiFunction;)Lkotlinx/serialization/cbor/CborElement;
+ public synthetic fun computeIfAbsent (Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object;
+ public fun computeIfAbsent (Lkotlinx/serialization/cbor/CborElement;Ljava/util/function/Function;)Lkotlinx/serialization/cbor/CborElement;
+ public synthetic fun computeIfPresent (Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object;
+ public fun computeIfPresent (Lkotlinx/serialization/cbor/CborElement;Ljava/util/function/BiFunction;)Lkotlinx/serialization/cbor/CborElement;
+ public final fun containsKey (Ljava/lang/Object;)Z
+ public fun containsKey (Lkotlinx/serialization/cbor/CborElement;)Z
+ public final fun containsValue (Ljava/lang/Object;)Z
+ public fun containsValue (Lkotlinx/serialization/cbor/CborElement;)Z
+ public final fun entrySet ()Ljava/util/Set;
+ public fun equals (Ljava/lang/Object;)Z
+ public final synthetic fun get (Ljava/lang/Object;)Ljava/lang/Object;
+ public final fun get (Ljava/lang/Object;)Lkotlinx/serialization/cbor/CborElement;
+ public fun get (Lkotlinx/serialization/cbor/CborElement;)Lkotlinx/serialization/cbor/CborElement;
+ public fun getEntries ()Ljava/util/Set;
+ public fun getKeys ()Ljava/util/Set;
+ public fun getSize ()I
+ public fun getValues ()Ljava/util/Collection;
+ public fun hashCode ()I
+ public fun isEmpty ()Z
+ public final fun keySet ()Ljava/util/Set;
+ public synthetic fun merge (Ljava/lang/Object;Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object;
+ public fun merge (Lkotlinx/serialization/cbor/CborElement;Lkotlinx/serialization/cbor/CborElement;Ljava/util/function/BiFunction;)Lkotlinx/serialization/cbor/CborElement;
+ public synthetic fun put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+ public fun put (Lkotlinx/serialization/cbor/CborElement;Lkotlinx/serialization/cbor/CborElement;)Lkotlinx/serialization/cbor/CborElement;
+ public fun putAll (Ljava/util/Map;)V
+ public synthetic fun putIfAbsent (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+ public fun putIfAbsent (Lkotlinx/serialization/cbor/CborElement;Lkotlinx/serialization/cbor/CborElement;)Lkotlinx/serialization/cbor/CborElement;
+ public synthetic fun remove (Ljava/lang/Object;)Ljava/lang/Object;
+ public fun remove (Ljava/lang/Object;)Lkotlinx/serialization/cbor/CborElement;
+ public fun remove (Ljava/lang/Object;Ljava/lang/Object;)Z
+ public synthetic fun replace (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+ public synthetic fun replace (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z
+ public fun replace (Lkotlinx/serialization/cbor/CborElement;Lkotlinx/serialization/cbor/CborElement;)Lkotlinx/serialization/cbor/CborElement;
+ public fun replace (Lkotlinx/serialization/cbor/CborElement;Lkotlinx/serialization/cbor/CborElement;Lkotlinx/serialization/cbor/CborElement;)Z
+ public fun replaceAll (Ljava/util/function/BiFunction;)V
+ public final fun size ()I
+ public fun toString ()Ljava/lang/String;
+ public final fun values ()Ljava/util/Collection;
+}
+
+public final class kotlinx/serialization/cbor/CborMap$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class kotlinx/serialization/cbor/CborNegativeInt : kotlinx/serialization/cbor/CborInt {
+ public static final field Companion Lkotlinx/serialization/cbor/CborNegativeInt$Companion;
+ public synthetic fun (J[JLkotlin/jvm/internal/DefaultConstructorMarker;)V
+}
+
+public final class kotlinx/serialization/cbor/CborNegativeInt$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class kotlinx/serialization/cbor/CborNull : kotlinx/serialization/cbor/CborPrimitive {
+ public static final field Companion Lkotlinx/serialization/cbor/CborNull$Companion;
+ public synthetic fun ([JLkotlin/jvm/internal/DefaultConstructorMarker;)V
+}
+
+public final class kotlinx/serialization/cbor/CborNull$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class kotlinx/serialization/cbor/CborPositiveInt : kotlinx/serialization/cbor/CborInt {
+ public static final field Companion Lkotlinx/serialization/cbor/CborPositiveInt$Companion;
+ public synthetic fun (J[JLkotlin/jvm/internal/DefaultConstructorMarker;)V
+}
+
+public final class kotlinx/serialization/cbor/CborPositiveInt$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public abstract class kotlinx/serialization/cbor/CborPrimitive : kotlinx/serialization/cbor/CborElement {
+ public static final field Companion Lkotlinx/serialization/cbor/CborPrimitive$Companion;
+ public synthetic fun (Ljava/lang/Object;[JILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public synthetic fun (Ljava/lang/Object;[JLkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getValue ()Ljava/lang/Object;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class kotlinx/serialization/cbor/CborPrimitive$Companion {
+ public final fun serializer (Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer;
+}
+
+public final class kotlinx/serialization/cbor/CborString : kotlinx/serialization/cbor/CborPrimitive {
+ public static final field Companion Lkotlinx/serialization/cbor/CborString$Companion;
+ public synthetic fun (Ljava/lang/String;[JLkotlin/jvm/internal/DefaultConstructorMarker;)V
+}
+
+public final class kotlinx/serialization/cbor/CborString$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
public final class kotlinx/serialization/cbor/CborTag {
public static final field BASE16 J
public static final field BASE64 J
diff --git a/formats/cbor/api/kotlinx-serialization-cbor.klib.api b/formats/cbor/api/kotlinx-serialization-cbor.klib.api
index 2658e3586c..afab2b9c14 100644
--- a/formats/cbor/api/kotlinx-serialization-cbor.klib.api
+++ b/formats/cbor/api/kotlinx-serialization-cbor.klib.api
@@ -45,6 +45,8 @@ open annotation class kotlinx.serialization.cbor/ValueTags : kotlin/Annotation {
abstract interface kotlinx.serialization.cbor/CborDecoder : kotlinx.serialization.encoding/Decoder { // kotlinx.serialization.cbor/CborDecoder|null[0]
abstract val cbor // kotlinx.serialization.cbor/CborDecoder.cbor|{}cbor[0]
abstract fun (): kotlinx.serialization.cbor/Cbor // kotlinx.serialization.cbor/CborDecoder.cbor.|(){}[0]
+
+ abstract fun decodeCborElement(): kotlinx.serialization.cbor/CborElement // kotlinx.serialization.cbor/CborDecoder.decodeCborElement|decodeCborElement(){}[0]
}
abstract interface kotlinx.serialization.cbor/CborEncoder : kotlinx.serialization.encoding/Encoder { // kotlinx.serialization.cbor/CborEncoder|null[0]
@@ -52,6 +54,14 @@ abstract interface kotlinx.serialization.cbor/CborEncoder : kotlinx.serializatio
abstract fun (): kotlinx.serialization.cbor/Cbor // kotlinx.serialization.cbor/CborEncoder.cbor.|(){}[0]
}
+final class kotlinx.serialization.cbor/CborBoolean : kotlinx.serialization.cbor/CborPrimitive { // kotlinx.serialization.cbor/CborBoolean|null[0]
+ constructor (kotlin/Boolean, kotlin/ULongArray...) // kotlinx.serialization.cbor/CborBoolean.|(kotlin.Boolean;kotlin.ULongArray...){}[0]
+
+ final object Companion { // kotlinx.serialization.cbor/CborBoolean.Companion|null[0]
+ final fun serializer(): kotlinx.serialization/KSerializer // kotlinx.serialization.cbor/CborBoolean.Companion.serializer|serializer(){}[0]
+ }
+}
+
final class kotlinx.serialization.cbor/CborBuilder { // kotlinx.serialization.cbor/CborBuilder|null[0]
final var alwaysUseByteString // kotlinx.serialization.cbor/CborBuilder.alwaysUseByteString|{}alwaysUseByteString[0]
final fun (): kotlin/Boolean // kotlinx.serialization.cbor/CborBuilder.alwaysUseByteString.|(){}[0]
@@ -91,6 +101,18 @@ final class kotlinx.serialization.cbor/CborBuilder { // kotlinx.serialization.cb
final fun (kotlin/Boolean) // kotlinx.serialization.cbor/CborBuilder.verifyValueTags.|(kotlin.Boolean){}[0]
}
+final class kotlinx.serialization.cbor/CborByteString : kotlinx.serialization.cbor/CborPrimitive { // kotlinx.serialization.cbor/CborByteString|null[0]
+ constructor (kotlin/ByteArray, kotlin/ULongArray...) // kotlinx.serialization.cbor/CborByteString.|(kotlin.ByteArray;kotlin.ULongArray...){}[0]
+
+ final fun equals(kotlin/Any?): kotlin/Boolean // kotlinx.serialization.cbor/CborByteString.equals|equals(kotlin.Any?){}[0]
+ final fun hashCode(): kotlin/Int // kotlinx.serialization.cbor/CborByteString.hashCode|hashCode(){}[0]
+ final fun toString(): kotlin/String // kotlinx.serialization.cbor/CborByteString.toString|toString(){}[0]
+
+ final object Companion { // kotlinx.serialization.cbor/CborByteString.Companion|null[0]
+ final fun serializer(): kotlinx.serialization/KSerializer // kotlinx.serialization.cbor/CborByteString.Companion.serializer|serializer(){}[0]
+ }
+}
+
final class kotlinx.serialization.cbor/CborConfiguration { // kotlinx.serialization.cbor/CborConfiguration|null[0]
final val alwaysUseByteString // kotlinx.serialization.cbor/CborConfiguration.alwaysUseByteString|{}alwaysUseByteString[0]
final fun (): kotlin/Boolean // kotlinx.serialization.cbor/CborConfiguration.alwaysUseByteString.|(){}[0]
@@ -118,12 +140,133 @@ final class kotlinx.serialization.cbor/CborConfiguration { // kotlinx.serializat
final fun toString(): kotlin/String // kotlinx.serialization.cbor/CborConfiguration.toString|toString(){}[0]
}
+final class kotlinx.serialization.cbor/CborFloat : kotlinx.serialization.cbor/CborPrimitive { // kotlinx.serialization.cbor/CborFloat|null[0]
+ constructor (kotlin/Double, kotlin/ULongArray...) // kotlinx.serialization.cbor/CborFloat.|(kotlin.Double;kotlin.ULongArray...){}[0]
+
+ final object Companion { // kotlinx.serialization.cbor/CborFloat.Companion|null[0]
+ final fun serializer(): kotlinx.serialization/KSerializer // kotlinx.serialization.cbor/CborFloat.Companion.serializer|serializer(){}[0]
+ }
+}
+
+final class kotlinx.serialization.cbor/CborList : kotlin.collections/List, kotlinx.serialization.cbor/CborElement { // kotlinx.serialization.cbor/CborList|null[0]
+ constructor (kotlin.collections/List, kotlin/ULongArray...) // kotlinx.serialization.cbor/CborList.|(kotlin.collections.List;kotlin.ULongArray...){}[0]
+
+ final val size // kotlinx.serialization.cbor/CborList.size|{}size[0]
+ final fun (): kotlin/Int // kotlinx.serialization.cbor/CborList.size.|(){}[0]
+
+ final fun contains(kotlinx.serialization.cbor/CborElement): kotlin/Boolean // kotlinx.serialization.cbor/CborList.contains|contains(kotlinx.serialization.cbor.CborElement){}[0]
+ final fun containsAll(kotlin.collections/Collection): kotlin/Boolean // kotlinx.serialization.cbor/CborList.containsAll|containsAll(kotlin.collections.Collection){}[0]
+ final fun equals(kotlin/Any?): kotlin/Boolean // kotlinx.serialization.cbor/CborList.equals|equals(kotlin.Any?){}[0]
+ final fun get(kotlin/Int): kotlinx.serialization.cbor/CborElement // kotlinx.serialization.cbor/CborList.get|get(kotlin.Int){}[0]
+ final fun hashCode(): kotlin/Int // kotlinx.serialization.cbor/CborList.hashCode|hashCode(){}[0]
+ final fun indexOf(kotlinx.serialization.cbor/CborElement): kotlin/Int // kotlinx.serialization.cbor/CborList.indexOf|indexOf(kotlinx.serialization.cbor.CborElement){}[0]
+ final fun isEmpty(): kotlin/Boolean // kotlinx.serialization.cbor/CborList.isEmpty|isEmpty(){}[0]
+ final fun iterator(): kotlin.collections/Iterator // kotlinx.serialization.cbor/CborList.iterator|iterator(){}[0]
+ final fun lastIndexOf(kotlinx.serialization.cbor/CborElement): kotlin/Int // kotlinx.serialization.cbor/CborList.lastIndexOf|lastIndexOf(kotlinx.serialization.cbor.CborElement){}[0]
+ final fun listIterator(): kotlin.collections/ListIterator // kotlinx.serialization.cbor/CborList.listIterator|listIterator(){}[0]
+ final fun listIterator(kotlin/Int): kotlin.collections/ListIterator // kotlinx.serialization.cbor/CborList.listIterator|listIterator(kotlin.Int){}[0]
+ final fun subList(kotlin/Int, kotlin/Int): kotlin.collections/List // kotlinx.serialization.cbor/CborList.subList|subList(kotlin.Int;kotlin.Int){}[0]
+ final fun toString(): kotlin/String // kotlinx.serialization.cbor/CborList.toString|toString(){}[0]
+
+ final object Companion { // kotlinx.serialization.cbor/CborList.Companion|null[0]
+ final fun serializer(): kotlinx.serialization/KSerializer // kotlinx.serialization.cbor/CborList.Companion.serializer|serializer(){}[0]
+ }
+
+ // Targets: [js]
+ final fun asJsReadonlyArrayView(): kotlin.js.collections/JsReadonlyArray // kotlinx.serialization.cbor/CborList.asJsReadonlyArrayView|asJsReadonlyArrayView(){}[0]
+}
+
+final class kotlinx.serialization.cbor/CborMap : kotlin.collections/Map, kotlinx.serialization.cbor/CborElement { // kotlinx.serialization.cbor/CborMap|null[0]
+ constructor (kotlin.collections/Map, kotlin/ULongArray...) // kotlinx.serialization.cbor/CborMap.|(kotlin.collections.Map;kotlin.ULongArray...){}[0]
+
+ final val entries // kotlinx.serialization.cbor/CborMap.entries|{}entries[0]
+ final fun (): kotlin.collections/Set> // kotlinx.serialization.cbor/CborMap.entries.|(){}[0]
+ final val keys // kotlinx.serialization.cbor/CborMap.keys|{}keys[0]
+ final fun (): kotlin.collections/Set // kotlinx.serialization.cbor/CborMap.keys.|(){}[0]
+ final val size // kotlinx.serialization.cbor/CborMap.size|{}size[0]
+ final fun (): kotlin/Int // kotlinx.serialization.cbor/CborMap.size.|(){}[0]
+ final val values // kotlinx.serialization.cbor/CborMap.values|{}values[0]
+ final fun (): kotlin.collections/Collection // kotlinx.serialization.cbor/CborMap.values.|(){}[0]
+
+ final fun containsKey(kotlinx.serialization.cbor/CborElement): kotlin/Boolean // kotlinx.serialization.cbor/CborMap.containsKey|containsKey(kotlinx.serialization.cbor.CborElement){}[0]
+ final fun containsValue(kotlinx.serialization.cbor/CborElement): kotlin/Boolean // kotlinx.serialization.cbor/CborMap.containsValue|containsValue(kotlinx.serialization.cbor.CborElement){}[0]
+ final fun equals(kotlin/Any?): kotlin/Boolean // kotlinx.serialization.cbor/CborMap.equals|equals(kotlin.Any?){}[0]
+ final fun get(kotlinx.serialization.cbor/CborElement): kotlinx.serialization.cbor/CborElement? // kotlinx.serialization.cbor/CborMap.get|get(kotlinx.serialization.cbor.CborElement){}[0]
+ final fun hashCode(): kotlin/Int // kotlinx.serialization.cbor/CborMap.hashCode|hashCode(){}[0]
+ final fun isEmpty(): kotlin/Boolean // kotlinx.serialization.cbor/CborMap.isEmpty|isEmpty(){}[0]
+ final fun toString(): kotlin/String // kotlinx.serialization.cbor/CborMap.toString|toString(){}[0]
+
+ final object Companion { // kotlinx.serialization.cbor/CborMap.Companion|null[0]
+ final fun serializer(): kotlinx.serialization/KSerializer // kotlinx.serialization.cbor/CborMap.Companion.serializer|serializer(){}[0]
+ }
+
+ // Targets: [js]
+ final fun asJsReadonlyMapView(): kotlin.js.collections/JsReadonlyMap // kotlinx.serialization.cbor/CborMap.asJsReadonlyMapView|asJsReadonlyMapView(){}[0]
+}
+
+final class kotlinx.serialization.cbor/CborNegativeInt : kotlinx.serialization.cbor/CborInt { // kotlinx.serialization.cbor/CborNegativeInt|null[0]
+ constructor (kotlin/Long, kotlin/ULongArray...) // kotlinx.serialization.cbor/CborNegativeInt.|(kotlin.Long;kotlin.ULongArray...){}[0]
+
+ final object Companion { // kotlinx.serialization.cbor/CborNegativeInt.Companion|null[0]
+ final fun serializer(): kotlinx.serialization/KSerializer // kotlinx.serialization.cbor/CborNegativeInt.Companion.serializer|serializer(){}[0]
+ }
+}
+
+final class kotlinx.serialization.cbor/CborNull : kotlinx.serialization.cbor/CborPrimitive { // kotlinx.serialization.cbor/CborNull|null[0]
+ constructor (kotlin/ULongArray...) // kotlinx.serialization.cbor/CborNull.|(kotlin.ULongArray...){}[0]
+
+ final object Companion { // kotlinx.serialization.cbor/CborNull.Companion|null[0]
+ final fun serializer(): kotlinx.serialization/KSerializer // kotlinx.serialization.cbor/CborNull.Companion.serializer|serializer(){}[0]
+ }
+}
+
+final class kotlinx.serialization.cbor/CborPositiveInt : kotlinx.serialization.cbor/CborInt { // kotlinx.serialization.cbor/CborPositiveInt|null[0]
+ constructor (kotlin/ULong, kotlin/ULongArray...) // kotlinx.serialization.cbor/CborPositiveInt.|(kotlin.ULong;kotlin.ULongArray...){}[0]
+
+ final object Companion { // kotlinx.serialization.cbor/CborPositiveInt.Companion|null[0]
+ final fun serializer(): kotlinx.serialization/KSerializer // kotlinx.serialization.cbor/CborPositiveInt.Companion.serializer|serializer(){}[0]
+ }
+}
+
+final class kotlinx.serialization.cbor/CborString : kotlinx.serialization.cbor/CborPrimitive { // kotlinx.serialization.cbor/CborString|null[0]
+ constructor (kotlin/String, kotlin/ULongArray...) // kotlinx.serialization.cbor/CborString.|(kotlin.String;kotlin.ULongArray...){}[0]
+
+ final object Companion { // kotlinx.serialization.cbor/CborString.Companion|null[0]
+ final fun serializer(): kotlinx.serialization/KSerializer // kotlinx.serialization.cbor/CborString.Companion.serializer|serializer(){}[0]
+ }
+}
+
+sealed class <#A: kotlin/Any> kotlinx.serialization.cbor/CborInt : kotlinx.serialization.cbor/CborPrimitive<#A> { // kotlinx.serialization.cbor/CborInt|null[0]
+ final object Companion : kotlinx.serialization.internal/SerializerFactory { // kotlinx.serialization.cbor/CborInt.Companion|null[0]
+ final fun <#A2: kotlin/Any?> serializer(kotlinx.serialization/KSerializer<#A2>): kotlinx.serialization/KSerializer> // kotlinx.serialization.cbor/CborInt.Companion.serializer|serializer(kotlinx.serialization.KSerializer<0:0>){0§}[0]
+ final fun invoke(kotlin/Long, kotlin/ULongArray...): kotlinx.serialization.cbor/CborInt<*> // kotlinx.serialization.cbor/CborInt.Companion.invoke|invoke(kotlin.Long;kotlin.ULongArray...){}[0]
+ final fun invoke(kotlin/ULong, kotlin/ULongArray...): kotlinx.serialization.cbor/CborInt // kotlinx.serialization.cbor/CborInt.Companion.invoke|invoke(kotlin.ULong;kotlin.ULongArray...){}[0]
+ final fun serializer(kotlin/Array>...): kotlinx.serialization/KSerializer<*> // kotlinx.serialization.cbor/CborInt.Companion.serializer|serializer(kotlin.Array>...){}[0]
+ }
+}
+
+sealed class <#A: kotlin/Any> kotlinx.serialization.cbor/CborPrimitive : kotlinx.serialization.cbor/CborElement { // kotlinx.serialization.cbor/CborPrimitive|null[0]
+ final val value // kotlinx.serialization.cbor/CborPrimitive.value|{}value[0]
+ final fun (): #A // kotlinx.serialization.cbor/CborPrimitive.value.|(){}[0]
+
+ open fun equals(kotlin/Any?): kotlin/Boolean // kotlinx.serialization.cbor/CborPrimitive.equals|equals(kotlin.Any?){}[0]
+ open fun hashCode(): kotlin/Int // kotlinx.serialization.cbor/CborPrimitive.hashCode|hashCode(){}[0]
+ open fun toString(): kotlin/String // kotlinx.serialization.cbor/CborPrimitive.toString|toString(){}[0]
+
+ final object Companion : kotlinx.serialization.internal/SerializerFactory { // kotlinx.serialization.cbor/CborPrimitive.Companion|null[0]
+ final fun <#A2: kotlin/Any?> serializer(kotlinx.serialization/KSerializer<#A2>): kotlinx.serialization/KSerializer> // kotlinx.serialization.cbor/CborPrimitive.Companion.serializer|serializer(kotlinx.serialization.KSerializer<0:0>){0§}[0]
+ final fun serializer(kotlin/Array>...): kotlinx.serialization/KSerializer<*> // kotlinx.serialization.cbor/CborPrimitive.Companion.serializer|serializer(kotlin.Array>...){}[0]
+ }
+}
+
sealed class kotlinx.serialization.cbor/Cbor : kotlinx.serialization/BinaryFormat { // kotlinx.serialization.cbor/Cbor|null[0]
final val configuration // kotlinx.serialization.cbor/Cbor.configuration|{}configuration[0]
final fun (): kotlinx.serialization.cbor/CborConfiguration // kotlinx.serialization.cbor/Cbor.configuration.|(){}[0]
open val serializersModule // kotlinx.serialization.cbor/Cbor.serializersModule|{}serializersModule[0]
open fun (): kotlinx.serialization.modules/SerializersModule // kotlinx.serialization.cbor/Cbor.serializersModule.|(){}[0]
+ final fun <#A1: kotlin/Any?> decodeFromCborElement(kotlinx.serialization/DeserializationStrategy<#A1>, kotlinx.serialization.cbor/CborElement): #A1 // kotlinx.serialization.cbor/Cbor.decodeFromCborElement|decodeFromCborElement(kotlinx.serialization.DeserializationStrategy<0:0>;kotlinx.serialization.cbor.CborElement){0§}[0]
+ final fun <#A1: kotlin/Any?> encodeToCborElement(kotlinx.serialization/SerializationStrategy<#A1>, #A1): kotlinx.serialization.cbor/CborElement // kotlinx.serialization.cbor/Cbor.encodeToCborElement|encodeToCborElement(kotlinx.serialization.SerializationStrategy<0:0>;0:0){0§}[0]
open fun <#A1: kotlin/Any?> decodeFromByteArray(kotlinx.serialization/DeserializationStrategy<#A1>, kotlin/ByteArray): #A1 // kotlinx.serialization.cbor/Cbor.decodeFromByteArray|decodeFromByteArray(kotlinx.serialization.DeserializationStrategy<0:0>;kotlin.ByteArray){0§}[0]
open fun <#A1: kotlin/Any?> encodeToByteArray(kotlinx.serialization/SerializationStrategy<#A1>, #A1): kotlin/ByteArray // kotlinx.serialization.cbor/Cbor.encodeToByteArray|encodeToByteArray(kotlinx.serialization.SerializationStrategy<0:0>;0:0){0§}[0]
@@ -133,6 +276,18 @@ sealed class kotlinx.serialization.cbor/Cbor : kotlinx.serialization/BinaryForma
}
}
+sealed class kotlinx.serialization.cbor/CborElement { // kotlinx.serialization.cbor/CborElement|null[0]
+ final var tags // kotlinx.serialization.cbor/CborElement.tags|{}tags[0]
+ final fun (): kotlin/ULongArray // kotlinx.serialization.cbor/CborElement.tags.|(){}[0]
+
+ open fun equals(kotlin/Any?): kotlin/Boolean // kotlinx.serialization.cbor/CborElement.equals|equals(kotlin.Any?){}[0]
+ open fun hashCode(): kotlin/Int // kotlinx.serialization.cbor/CborElement.hashCode|hashCode(){}[0]
+
+ final object Companion { // kotlinx.serialization.cbor/CborElement.Companion|null[0]
+ final fun serializer(): kotlinx.serialization/KSerializer // kotlinx.serialization.cbor/CborElement.Companion.serializer|serializer(){}[0]
+ }
+}
+
final object kotlinx.serialization.cbor/CborTag { // kotlinx.serialization.cbor/CborTag|null[0]
final const val BASE16 // kotlinx.serialization.cbor/CborTag.BASE16|{}BASE16[0]
final fun (): kotlin/ULong // kotlinx.serialization.cbor/CborTag.BASE16.|(){}[0]
@@ -169,3 +324,5 @@ final object kotlinx.serialization.cbor/CborTag { // kotlinx.serialization.cbor/
}
final fun kotlinx.serialization.cbor/Cbor(kotlinx.serialization.cbor/Cbor = ..., kotlin/Function1): kotlinx.serialization.cbor/Cbor // kotlinx.serialization.cbor/Cbor|Cbor(kotlinx.serialization.cbor.Cbor;kotlin.Function1){}[0]
+final inline fun <#A: reified kotlin/Any?> (kotlinx.serialization.cbor/Cbor).kotlinx.serialization.cbor/decodeFromCborElement(kotlinx.serialization.cbor/CborElement): #A // kotlinx.serialization.cbor/decodeFromCborElement|decodeFromCborElement@kotlinx.serialization.cbor.Cbor(kotlinx.serialization.cbor.CborElement){0§}[0]
+final inline fun <#A: reified kotlin/Any?> (kotlinx.serialization.cbor/Cbor).kotlinx.serialization.cbor/encodeToCborElement(#A): kotlinx.serialization.cbor/CborElement // kotlinx.serialization.cbor/encodeToCborElement|encodeToCborElement@kotlinx.serialization.cbor.Cbor(0:0){0§}[0]
diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/Cbor.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/Cbor.kt
index 21293a9231..c23c288869 100644
--- a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/Cbor.kt
+++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/Cbor.kt
@@ -88,7 +88,49 @@ public sealed class Cbor(
val reader = CborReader(this, CborParser(stream, configuration.verifyObjectTags))
return reader.decodeSerializableValue(deserializer)
}
+
+ /**
+ * Deserializes the given [element] into a value of type [T] using the given [deserializer].
+ *
+ * @throws [SerializationException] if the given CBOR element is not a valid CBOR input for the type [T]
+ * @throws [IllegalArgumentException] if the decoded input cannot be represented as a valid instance of type [T]
+ */
+ public fun decodeFromCborElement(deserializer: DeserializationStrategy, element: CborElement): T {
+ val reader = CborReader(this, StructuredCborParser(element, configuration.verifyObjectTags))
+ return reader.decodeSerializableValue(deserializer)
+ }
+
+ /**
+ * Serializes the given [value] into an equivalent [CborElement] using the given [serializer]
+ *
+ * @throws [SerializationException] if the given value cannot be serialized to CBOR
+ */
+ public fun encodeToCborElement(serializer: SerializationStrategy, value: T): CborElement {
+ val writer = StructuredCborWriter(this)
+ writer.encodeSerializableValue(serializer, value)
+ return writer.finalize()
+ }
}
+/**
+ * Serializes the given [value] into an equivalent [CborElement] using a serializer retrieved
+ * from reified type parameter.
+ *
+ * @throws [SerializationException] if the given value cannot be serialized to CBOR.
+ */
+@ExperimentalSerializationApi
+public inline fun Cbor.encodeToCborElement(value: T): CborElement =
+ encodeToCborElement(serializersModule.serializer(), value)
+
+/**
+ * Deserializes the given [element] element into a value of type [T] using a deserializer retrieved
+ * from reified type parameter.
+ *
+ * @throws [SerializationException] if the given JSON element is not a valid CBOR input for the type [T]
+ * @throws [IllegalArgumentException] if the decoded input cannot be represented as a valid instance of type [T]
+ */
+@ExperimentalSerializationApi
+public inline fun Cbor.decodeFromCborElement(element: CborElement): T =
+ decodeFromCborElement(serializersModule.serializer(), element)
@OptIn(ExperimentalSerializationApi::class)
private class CborImpl(
@@ -108,18 +150,20 @@ private class CborImpl(
public fun Cbor(from: Cbor = Cbor, builderAction: CborBuilder.() -> Unit): Cbor {
val builder = CborBuilder(from)
builder.builderAction()
- return CborImpl(CborConfiguration(
- builder.encodeDefaults,
- builder.ignoreUnknownKeys,
- builder.encodeKeyTags,
- builder.encodeValueTags,
- builder.encodeObjectTags,
- builder.verifyKeyTags,
- builder.verifyValueTags,
- builder.verifyObjectTags,
- builder.useDefiniteLengthEncoding,
- builder.preferCborLabelsOverNames,
- builder.alwaysUseByteString),
+ return CborImpl(
+ CborConfiguration(
+ builder.encodeDefaults,
+ builder.ignoreUnknownKeys,
+ builder.encodeKeyTags,
+ builder.encodeValueTags,
+ builder.encodeObjectTags,
+ builder.verifyKeyTags,
+ builder.verifyValueTags,
+ builder.verifyObjectTags,
+ builder.useDefiniteLengthEncoding,
+ builder.preferCborLabelsOverNames,
+ builder.alwaysUseByteString
+ ),
builder.serializersModule
)
}
diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/CborDecoder.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/CborDecoder.kt
index 13a773f3fa..3a3de2279c 100644
--- a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/CborDecoder.kt
+++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/CborDecoder.kt
@@ -31,4 +31,15 @@ public interface CborDecoder : Decoder {
* Exposes the current [Cbor] instance and all its configuration flags. Useful for low-level custom serializers.
*/
public val cbor: Cbor
+
+ /**
+ * Decodes the next element in the current input as [CborElement].
+ * The type of the decoded element depends on the current state of the input and, when received
+ * by [serializer][KSerializer] in its [KSerializer.serialize] method, the type of the token directly matches
+ * the [kind][kotlinx.serialization.descriptors.SerialDescriptor.kind].
+ *
+ * This method is allowed to invoke only as the part of the whole deserialization process of the class,
+ * calling this method after invoking [beginStructure] or any `decode*` method will lead to unspecified behaviour.
+ */
+ public fun decodeCborElement(): CborElement
}
diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/CborElement.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/CborElement.kt
new file mode 100644
index 0000000000..bb89c1e020
--- /dev/null
+++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/CborElement.kt
@@ -0,0 +1,261 @@
+/*
+ * Copyright 2017-2025 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("unused")
+@file:OptIn(ExperimentalUnsignedTypes::class)
+
+package kotlinx.serialization.cbor
+
+import kotlinx.serialization.*
+import kotlinx.serialization.cbor.internal.*
+
+/**
+ * Class representing single CBOR element.
+ * Can be [CborPrimitive], [CborMap] or [CborList].
+ *
+ * [CborElement.toString] properly prints CBOR tree as a human-readable representation.
+ * Whole hierarchy is serializable, but only when used with [Cbor] as [CborElement] is purely CBOR-specific structure
+ * which has meaningful schemaless semantics only for CBOR.
+ *
+ * The whole hierarchy is [serializable][Serializable] only by [Cbor] format.
+ */
+@Serializable(with = CborElementSerializer::class)
+public sealed class CborElement(
+ /**
+ * CBOR tags associated with this element.
+ * Tags are optional semantic tagging of other major types (major type 6).
+ * See [RFC 8949 3.4. Tagging of Items](https://datatracker.ietf.org/doc/html/rfc8949#name-tagging-of-items).
+ */
+ @OptIn(ExperimentalUnsignedTypes::class)
+ tags: ULongArray = ulongArrayOf()
+
+) {
+ /**
+ * CBOR tags associated with this element.
+ * Tags are optional semantic tagging of other major types (major type 6).
+ * See [RFC 8949 3.4. Tagging of Items](https://datatracker.ietf.org/doc/html/rfc8949#name-tagging-of-items).
+ */
+ @OptIn(ExperimentalUnsignedTypes::class)
+ public var tags: ULongArray = tags
+ internal set //need this to collect
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is CborElement) return false
+
+ if (!tags.contentEquals(other.tags)) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return tags.contentHashCode()
+ }
+
+}
+
+/**
+ * Class representing CBOR primitive value.
+ * CBOR primitives include numbers, strings, booleans, byte arrays and special null value [CborNull].
+ */
+@Serializable(with = CborPrimitiveSerializer::class)
+public sealed class CborPrimitive(
+ public val value: T,
+ tags: ULongArray = ulongArrayOf()
+) : CborElement(tags) {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is CborPrimitive<*>) return false
+ if (!super.equals(other)) return false
+
+ if (value != other.value) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = super.hashCode()
+ result = 31 * result + value.hashCode()
+ return result
+ }
+
+ override fun toString(): String {
+ return "${this::class.simpleName}(" +
+ "tags=${tags.joinToString(prefix = "[", postfix = "]")}, " +
+ "value=$value" +
+ ")"
+ }
+}
+
+/**
+ * Class representing either:
+ * * signed CBOR integer (major type 1)
+ * * unsigned CBOR integer (major type 0)
+ *
+ * depending on whether a positive or a negative number was passed.
+ */
+@Serializable(with = CborIntSerializer::class)
+public sealed class CborInt(
+ tags: ULongArray = ulongArrayOf(),
+ value: T,
+) : CborPrimitive(value, tags) {
+ public companion object {
+ /**
+ * Creates:
+ * * signed CBOR integer (major type 1)
+ * * unsigned CBOR integer (major type 0)
+ *
+ * depending on whether a positive or a negative number was passed.
+ */
+ public operator fun invoke(
+ value: Long,
+ vararg tags: ULong
+ ): CborInt<*> =
+ if (value >= 0) CborPositiveInt(value.toULong(), tags = tags) else CborNegativeInt(value, tags = tags)
+
+ /**
+ * Creates an unsigned CBOR integer (major type 0).
+ */
+ public operator fun invoke(
+ value: ULong,
+ vararg tags: ULong
+ ): CborInt = CborPositiveInt(value, tags = tags)
+ }
+}
+
+/**
+ * Class representing signed CBOR integer (major type 1).
+ */
+@Serializable(with = CborNegativeIntSerializer::class)
+public class CborNegativeInt(
+ value: Long,
+ vararg tags: ULong
+) : CborInt(tags, value) {
+ init {
+ require(value < 0) { "Number must be negative: $value" }
+ }
+}
+
+/**
+ * Class representing unsigned CBOR integer (major type 0).
+ */
+@Serializable(with = CborPositiveIntSerializer::class)
+public class CborPositiveInt(
+ value: ULong,
+ vararg tags: ULong
+) : CborInt(tags, value)
+
+/**
+ * Class representing CBOR floating point value (major type 7).
+ */
+@Serializable(with = CborFloatSerializer::class)
+public class CborFloat(
+ value: Double,
+ vararg tags: ULong
+) : CborPrimitive(value, tags)
+
+/**
+ * Class representing CBOR string value.
+ */
+@Serializable(with = CborStringSerializer::class)
+public class CborString(
+ value: String,
+ vararg tags: ULong
+) : CborPrimitive(value, tags)
+
+/**
+ * Class representing CBOR boolean value.
+ */
+@Serializable(with = CborBooleanSerializer::class)
+public class CborBoolean(
+ value: Boolean,
+ vararg tags: ULong
+) : CborPrimitive(value, tags)
+
+/**
+ * Class representing CBOR byte string value.
+ */
+@Serializable(with = CborByteStringSerializer::class)
+public class CborByteString(
+ value: ByteArray,
+ vararg tags: ULong
+) : CborPrimitive(value, tags) {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is CborByteString) return false
+ if (!tags.contentEquals(other.tags)) return false
+ return value.contentEquals(other.value)
+ }
+
+ override fun hashCode(): Int {
+ var result = tags.contentHashCode()
+ result = 31 * result + (value.contentHashCode())
+ return result
+ }
+
+ override fun toString(): String {
+ return "CborByteString(" +
+ "tags=${tags.joinToString(prefix = "[", postfix = "]")}, " +
+ "value=h'${value.toHexString()}" +
+ ")"
+ }
+}
+
+/**
+ * Class representing CBOR `null` value
+ */
+@Serializable(with = CborNullSerializer::class)
+public class CborNull(vararg tags: ULong) : CborPrimitive(Unit, tags)
+
+/**
+ * Class representing CBOR map, consisting of key-value pairs, where both key and value are arbitrary [CborElement]
+ *
+ * Since this class also implements [Map] interface, you can use
+ * traditional methods like [Map.get] or [Map.getValue] to obtain CBOR elements.
+ */
+@Serializable(with = CborMapSerializer::class)
+public class CborMap(
+ private val content: Map,
+ vararg tags: ULong
+) : CborElement(tags), Map by content {
+
+ public override fun equals(other: Any?): Boolean =
+ other is CborMap && other.content == content && other.tags.contentEquals(tags)
+
+ public override fun hashCode(): Int = content.hashCode() * 31 + tags.contentHashCode()
+
+ override fun toString(): String {
+ return "CborMap(" +
+ "tags=${tags.joinToString(prefix = "[", postfix = "]")}, " +
+ "content=$content" +
+ ")"
+ }
+
+}
+
+/**
+ * Class representing CBOR array, consisting of CBOR elements.
+ *
+ * Since this class also implements [List] interface, you can use
+ * traditional methods like [List.get] or [List.size] to obtain CBOR elements.
+ */
+@Serializable(with = CborListSerializer::class)
+public class CborList(
+ private val content: List,
+ vararg tags: ULong
+) : CborElement(tags), List by content {
+
+ public override fun equals(other: Any?): Boolean =
+ other is CborList && other.content == content && other.tags.contentEquals(tags)
+
+ public override fun hashCode(): Int = content.hashCode() * 31 + tags.contentHashCode()
+
+ override fun toString(): String {
+ return "CborList(" +
+ "tags=${tags.joinToString(prefix = "[", postfix = "]")}, " +
+ "content=$content" +
+ ")"
+ }
+
+}
\ No newline at end of file
diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/CborElementSerializers.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/CborElementSerializers.kt
new file mode 100644
index 0000000000..d466fcf142
--- /dev/null
+++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/CborElementSerializers.kt
@@ -0,0 +1,329 @@
+/*
+ * Copyright 2017-2025 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+@file:OptIn(ExperimentalSerializationApi::class, ExperimentalUnsignedTypes::class)
+
+package kotlinx.serialization.cbor.internal
+
+import kotlinx.serialization.*
+import kotlinx.serialization.builtins.*
+import kotlinx.serialization.cbor.*
+import kotlinx.serialization.descriptors.*
+import kotlinx.serialization.encoding.*
+
+internal interface CborSerializer
+
+/**
+ * Serializer object providing [SerializationStrategy] and [DeserializationStrategy] for [CborElement].
+ * It can only be used by with [Cbor] format and its input ([CborDecoder] and [CborEncoder]).
+ */
+internal object CborElementSerializer : KSerializer, CborSerializer {
+ override val descriptor: SerialDescriptor =
+ buildSerialDescriptor("kotlinx.serialization.cbor.CborElement", PolymorphicKind.SEALED) {
+ // Resolve cyclic dependency in descriptors by late binding
+ element("CborPrimitive", defer { CborPrimitiveSerializer.descriptor })
+ element("CborNull", defer { CborNullSerializer.descriptor })
+ element("CborString", defer { CborStringSerializer.descriptor })
+ element("CborBoolean", defer { CborBooleanSerializer.descriptor })
+ element("CborByteString", defer { CborByteStringSerializer.descriptor })
+ element("CborMap", defer { CborMapSerializer.descriptor })
+ element("CborList", defer { CborListSerializer.descriptor })
+ element("CborDouble", defer { CborFloatSerializer.descriptor })
+ element("CborInt", defer { CborNegativeIntSerializer.descriptor })
+ element("CborUInt", defer { CborPositiveIntSerializer.descriptor })
+ }
+
+ override fun serialize(encoder: Encoder, value: CborElement) {
+ encoder.asCborEncoder()
+
+ // Encode the value
+ when (value) {
+ is CborPrimitive<*> -> encoder.encodeSerializableValue(CborPrimitiveSerializer, value)
+ is CborMap -> encoder.encodeSerializableValue(CborMapSerializer, value)
+ is CborList -> encoder.encodeSerializableValue(CborListSerializer, value)
+ }
+ }
+
+ override fun deserialize(decoder: Decoder): CborElement {
+ val input = decoder.asCborDecoder()
+ return input.decodeCborElement()
+ }
+}
+
+/**
+ * Serializer object providing [SerializationStrategy] and [DeserializationStrategy] for [CborPrimitive].
+ * It can only be used by with [Cbor] format an its input ([CborDecoder] and [CborEncoder]).
+ */
+internal object CborPrimitiveSerializer : KSerializer>, CborSerializer {
+ override val descriptor: SerialDescriptor =
+ buildSerialDescriptor("kotlinx.serialization.cbor.CborPrimitive", PolymorphicKind.SEALED)
+
+ override fun serialize(encoder: Encoder, value: CborPrimitive<*>) {
+ when (value) {
+ is CborNull -> encoder.encodeSerializableValue(CborNullSerializer, value)
+ is CborString -> encoder.encodeSerializableValue(CborStringSerializer, value)
+ is CborBoolean -> encoder.encodeSerializableValue(CborBooleanSerializer, value)
+ is CborByteString -> encoder.encodeSerializableValue(CborByteStringSerializer, value)
+ is CborFloat -> encoder.encodeSerializableValue(CborFloatSerializer, value)
+ is CborNegativeInt -> encoder.encodeSerializableValue(CborNegativeIntSerializer, value)
+ is CborPositiveInt -> encoder.encodeSerializableValue(CborPositiveIntSerializer, value)
+ }
+ }
+
+ override fun deserialize(decoder: Decoder): CborPrimitive<*> {
+ val result = decoder.asCborDecoder().decodeCborElement()
+ if (result !is CborPrimitive<*>) throw CborDecodingException("Unexpected CBOR element, expected CborPrimitive, had ${result::class}")
+ return result
+ }
+}
+
+/**
+ * Serializer object providing [SerializationStrategy] and [DeserializationStrategy] for [CborNull].
+ * It can only be used by with [Cbor] format an its input ([CborDecoder] and [CborEncoder]).
+ */
+internal object CborNullSerializer : KSerializer, CborSerializer {
+
+ override val descriptor: SerialDescriptor =
+ buildSerialDescriptor("kotlinx.serialization.cbor.CborNull", SerialKind.ENUM)
+
+ override fun serialize(encoder: Encoder, value: CborNull) {
+ val cborEncoder = encoder.asCborEncoder()
+ cborEncoder.encodeTags(value)
+ encoder.encodeNull()
+ }
+
+ override fun deserialize(decoder: Decoder): CborNull {
+ decoder.asCborDecoder()
+ if (decoder.decodeNotNullMark()) {
+ throw CborDecodingException("Expected 'null' literal")
+ }
+
+ decoder.decodeNull()
+ return CborNull()
+ }
+}
+
+
+internal object CborIntSerializer : KSerializer>, CborSerializer {
+ override val descriptor: SerialDescriptor =
+ PrimitiveSerialDescriptor("kotlinx.serialization.cbor.CborInt", PrimitiveKind.LONG)
+
+ override fun serialize(encoder: Encoder, value: CborInt<*>) {
+ when (value) {
+ is CborNegativeInt -> encoder.encodeSerializableValue(CborNegativeIntSerializer, value)
+ is CborPositiveInt -> encoder.encodeSerializableValue(CborPositiveIntSerializer, value)
+ }
+ }
+
+ override fun deserialize(decoder: Decoder): CborInt<*> {
+ val result = decoder.asCborDecoder().decodeCborElement()
+ if (result !is CborInt<*>) throw CborDecodingException("Unexpected CBOR element, expected CborInt, had ${result::class}")
+ return result
+ }
+}
+
+internal object CborNegativeIntSerializer : KSerializer, CborSerializer {
+ override val descriptor: SerialDescriptor =
+ PrimitiveSerialDescriptor("kotlinx.serialization.cbor.CborNegativeInt", PrimitiveKind.LONG)
+
+ override fun serialize(encoder: Encoder, value: CborNegativeInt) {
+ val cborEncoder = encoder.asCborEncoder()
+ cborEncoder.encodeTags(value)
+ encoder.encodeLong(value.value)
+ }
+
+ override fun deserialize(decoder: Decoder): CborNegativeInt {
+ decoder.asCborDecoder()
+ return CborNegativeInt(decoder.decodeLong())
+ }
+}
+
+internal object CborPositiveIntSerializer : KSerializer, CborSerializer {
+ override val descriptor: SerialDescriptor =
+ PrimitiveSerialDescriptor("kotlinx.serialization.cbor.CborPositiveInt", PrimitiveKind.LONG)
+
+ override fun serialize(encoder: Encoder, value: CborPositiveInt) {
+ val cborEncoder = encoder.asCborEncoder()
+ cborEncoder.encodeTags(value)
+ encoder.encodeInline(descriptor).encodeSerializableValue(ULong.serializer(), value.value as ULong)
+ }
+
+ override fun deserialize(decoder: Decoder): CborPositiveInt {
+ decoder.asCborDecoder()
+ return CborPositiveInt(decoder.decodeInline(descriptor).decodeSerializableValue(ULong.serializer()))
+ }
+}
+
+internal object CborFloatSerializer : KSerializer, CborSerializer {
+ override val descriptor: SerialDescriptor =
+ PrimitiveSerialDescriptor("kotlinx.serialization.cbor.CborDouble", PrimitiveKind.DOUBLE)
+
+ override fun serialize(encoder: Encoder, value: CborFloat) {
+ val cborEncoder = encoder.asCborEncoder()
+ cborEncoder.encodeTags(value)
+ encoder.encodeDouble(value.value)
+ }
+
+ override fun deserialize(decoder: Decoder): CborFloat {
+ decoder.asCborDecoder()
+ return CborFloat(decoder.decodeDouble())
+ }
+}
+
+/**
+ * Serializer object providing [SerializationStrategy] and [DeserializationStrategy] for [CborString].
+ * It can only be used by with [Cbor] format an its input ([CborDecoder] and [CborEncoder]).
+ */
+internal object CborStringSerializer : KSerializer, CborSerializer {
+ override val descriptor: SerialDescriptor =
+ PrimitiveSerialDescriptor("kotlinx.serialization.cbor.CborString", PrimitiveKind.STRING)
+
+ override fun serialize(encoder: Encoder, value: CborString) {
+ val cborEncoder = encoder.asCborEncoder()
+ cborEncoder.encodeTags(value)
+ encoder.encodeString(value.value)
+ }
+
+ override fun deserialize(decoder: Decoder): CborString {
+ val cborDecoder = decoder.asCborDecoder()
+ val element = cborDecoder.decodeCborElement()
+ if (element !is CborString) throw CborDecodingException("Unexpected CBOR element, expected CborString, had ${element::class}")
+ return element
+ }
+}
+
+/**
+ * Serializer object providing [SerializationStrategy] and [DeserializationStrategy] for [CborBoolean].
+ * It can only be used by with [Cbor] format an its input ([CborDecoder] and [CborEncoder]).
+ */
+internal object CborBooleanSerializer : KSerializer, CborSerializer {
+ override val descriptor: SerialDescriptor =
+ PrimitiveSerialDescriptor("kotlinx.serialization.cbor.CborBoolean", PrimitiveKind.BOOLEAN)
+
+ override fun serialize(encoder: Encoder, value: CborBoolean) {
+ val cborEncoder = encoder.asCborEncoder()
+ cborEncoder.encodeTags(value)
+ encoder.encodeBoolean(value.value)
+ }
+
+ override fun deserialize(decoder: Decoder): CborBoolean {
+ val cborDecoder = decoder.asCborDecoder()
+ val element = cborDecoder.decodeCborElement()
+ if (element !is CborBoolean) throw CborDecodingException("Unexpected CBOR element, expected CborBoolean, had ${element::class}")
+ return element
+ }
+}
+
+/**
+ * Serializer object providing [SerializationStrategy] and [DeserializationStrategy] for [CborByteString].
+ * It can only be used by with [Cbor] format and its input ([CborDecoder] and [CborEncoder]).
+ */
+internal object CborByteStringSerializer : KSerializer, CborSerializer {
+ override val descriptor: SerialDescriptor =
+ PrimitiveSerialDescriptor("kotlinx.serialization.cbor.CborByteString", PrimitiveKind.STRING)
+
+ override fun serialize(encoder: Encoder, value: CborByteString) {
+ val cborEncoder = encoder.asCborEncoder()
+ cborEncoder.encodeTags(value)
+ cborEncoder.encodeByteString(value.value)
+ }
+
+ override fun deserialize(decoder: Decoder): CborByteString {
+ val cborDecoder = decoder.asCborDecoder()
+ val element = cborDecoder.decodeCborElement()
+ if (element !is CborByteString) throw CborDecodingException("Unexpected CBOR element, expected CborByteString, had ${element::class}")
+ return element
+ }
+}
+
+/**
+ * Serializer object providing [SerializationStrategy] and [DeserializationStrategy] for [CborMap].
+ * It can only be used by with [Cbor] format and its input ([CborDecoder] and [CborEncoder]).
+ */
+internal object CborMapSerializer : KSerializer, CborSerializer {
+ private object CborMapDescriptor :
+ SerialDescriptor by MapSerializer(CborElementSerializer, CborElementSerializer).descriptor {
+ @ExperimentalSerializationApi
+ override val serialName: String = "kotlinx.serialization.cbor.CborMap"
+ }
+
+ override val descriptor: SerialDescriptor = CborMapDescriptor
+
+ override fun serialize(encoder: Encoder, value: CborMap) {
+ val cborEncoder = encoder.asCborEncoder()
+ cborEncoder.encodeTags(value)
+ MapSerializer(CborElementSerializer, CborElementSerializer).serialize(encoder, value)
+ }
+
+ override fun deserialize(decoder: Decoder): CborMap {
+ decoder.asCborDecoder()
+ return CborMap(MapSerializer(CborElementSerializer, CborElementSerializer).deserialize(decoder))
+ }
+}
+
+/**
+ * Serializer object providing [SerializationStrategy] and [DeserializationStrategy] for [CborList].
+ * It can only be used by with [Cbor] format an its input ([CborDecoder] and [CborEncoder]).
+ */
+internal object CborListSerializer : KSerializer, CborSerializer {
+ private object CborListDescriptor : SerialDescriptor by ListSerializer(CborElementSerializer).descriptor {
+ @ExperimentalSerializationApi
+ override val serialName: String = "kotlinx.serialization.cbor.CborList"
+ }
+
+ override val descriptor: SerialDescriptor = CborListDescriptor
+
+ override fun serialize(encoder: Encoder, value: CborList) {
+ val cborEncoder = encoder.asCborEncoder()
+ cborEncoder.encodeTags(value)
+ ListSerializer(CborElementSerializer).serialize(encoder, value)
+ }
+
+ override fun deserialize(decoder: Decoder): CborList {
+ decoder.asCborDecoder()
+ return CborList(ListSerializer(CborElementSerializer).deserialize(decoder))
+ }
+}
+
+
+internal fun Decoder.asCborDecoder(): CborDecoder = this as? CborDecoder
+ ?: throw IllegalStateException(
+ "This serializer can be used only with Cbor format." +
+ "Expected Decoder to be CborDecoder, got ${this::class}"
+ )
+
+/*need to expose writer to access encodeTag()*/
+internal fun Encoder.asCborEncoder() = this as? CborWriter
+ ?: throw IllegalStateException(
+ "This serializer can be used only with Cbor format." +
+ "Expected Encoder to be CborEncoder, got ${this::class}"
+ )
+
+/**
+ * Returns serial descriptor that delegates all the calls to descriptor returned by [deferred] block.
+ * Used to resolve cyclic dependencies between recursive serializable structures.
+ */
+@OptIn(ExperimentalSerializationApi::class)
+private fun defer(deferred: () -> SerialDescriptor): SerialDescriptor = object : SerialDescriptor {
+ private val original: SerialDescriptor by lazy(deferred)
+
+ override val serialName: String
+ get() = original.serialName
+ override val kind: SerialKind
+ get() = original.kind
+ override val elementsCount: Int
+ get() = original.elementsCount
+
+ override fun getElementName(index: Int): String = original.getElementName(index)
+ override fun getElementIndex(name: String): Int = original.getElementIndex(name)
+ override fun getElementAnnotations(index: Int): List = original.getElementAnnotations(index)
+ override fun getElementDescriptor(index: Int): SerialDescriptor = original.getElementDescriptor(index)
+ override fun isElementOptional(index: Int): Boolean = original.isElementOptional(index)
+}
+
+private fun CborWriter.encodeTags(value: CborElement) { // Encode tags if present
+ if (value.tags.isNotEmpty()) {
+ encodeTags(value.tags)
+ }
+
+}
\ No newline at end of file
diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/CborParserInterface.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/CborParserInterface.kt
new file mode 100644
index 0000000000..f3f4b2fbb2
--- /dev/null
+++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/CborParserInterface.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017-2025 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+@file:OptIn(ExperimentalSerializationApi::class, ExperimentalUnsignedTypes::class)
+
+package kotlinx.serialization.cbor.internal
+
+import kotlinx.serialization.*
+import kotlinx.serialization.cbor.CborList
+
+/**
+ * Common interface for CBOR parsers that can read CBOR data from different sources.
+ */
+internal sealed interface CborParserInterface {
+ // Basic state checks
+ fun isNull(): Boolean
+ fun isEnd(): Boolean
+ fun end()
+
+ // Collection operations
+ fun startArray(tags: ULongArray? = null): Int
+ fun startMap(tags: ULongArray? = null): Int
+
+ // Value reading operations
+ fun nextNull(tags: ULongArray? = null): Nothing?
+ fun nextBoolean(tags: ULongArray? = null): Boolean
+ fun nextNumber(tags: ULongArray? = null): Long
+ fun nextString(tags: ULongArray? = null): String
+ fun nextByteString(tags: ULongArray? = null): ByteArray
+ fun nextDouble(tags: ULongArray? = null): Double
+ fun nextFloat(tags: ULongArray? = null): Float
+
+ // Map key operations
+ fun nextTaggedStringOrNumber(): Triple
+
+ // Skip operations
+ //used only to skip unknown elements
+ fun skipElement(tags: ULongArray?)
+
+ // Tag verification
+ fun verifyTagsAndThrow(expected: ULongArray, actual: ULongArray?)
+
+ fun processTags(tags: ULongArray?): ULongArray?
+}
\ No newline at end of file
diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/CborTreeReader.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/CborTreeReader.kt
new file mode 100644
index 0000000000..d86295e7e3
--- /dev/null
+++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/CborTreeReader.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2017-2025 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+@file:OptIn(ExperimentalSerializationApi::class, ExperimentalUnsignedTypes::class)
+
+package kotlinx.serialization.cbor.internal
+
+import kotlinx.serialization.*
+import kotlinx.serialization.cbor.*
+
+/**
+ * [CborTreeReader] reads CBOR data from [parser] and constructs a [CborElement] tree.
+ */
+internal class CborTreeReader(
+ //no config values make sense here, because we have no "schema".
+ //we cannot validate tags, or disregard nulls, can we?!
+ //still, this needs to go here, in case it evolves to a point where we need to respect certain config values
+ private val configuration: CborConfiguration,
+ private val parser: CborParser
+) {
+ /**
+ * Reads the next CBOR element from the parser.
+ */
+ fun read(): CborElement {
+ // Read any tags before the actual value
+ val tags = readTags()
+
+ val result = when (parser.curByte shr 5) { // Get major type from the first 3 bits
+ 0 -> { // Major type 0: unsigned integer
+ val value = parser.nextNumber()
+ CborPositiveInt(value.toULong(), tags = tags)
+ }
+
+ 1 -> { // Major type 1: negative integer
+ val value = parser.nextNumber()
+ CborNegativeInt(value, tags = tags)
+ }
+
+ 2 -> { // Major type 2: byte string
+ CborByteString(parser.nextByteString(), tags = tags)
+ }
+
+ 3 -> { // Major type 3: text string
+ CborString(parser.nextString(), tags = tags)
+ }
+
+ 4 -> { // Major type 4: array
+ readArray(tags)
+ }
+
+ 5 -> { // Major type 5: map
+ readMap(tags)
+ }
+
+ 7 -> { // Major type 7: simple/float/break
+ when (parser.curByte) {
+ 0xF4 -> {
+ parser.readByte() // Advance parser position
+ CborBoolean(false, tags = tags)
+ }
+
+ 0xF5 -> {
+ parser.readByte() // Advance parser position
+ CborBoolean(true, tags = tags)
+ }
+
+ 0xF6, 0xF7 -> {
+ parser.nextNull()
+ CborNull(tags = tags)
+ }
+ // Half/Float32/Float64
+ NEXT_HALF, NEXT_FLOAT, NEXT_DOUBLE -> CborFloat(parser.nextDouble(), tags = tags)
+ else -> throw CborDecodingException(
+ "Invalid simple value or float type: ${parser.curByte.toString(16).uppercase()}"
+ )
+ }
+ }
+
+ else -> {
+ val errByte = parser.curByte shr 5
+ throw if (errByte == -1) CborDecodingException("Unexpected EOF")
+ else CborDecodingException("Invalid CBOR major type: $errByte")
+ }
+ }
+ return result
+ }
+
+ /**
+ * Reads any tags preceding the current value.
+ * @return An array of tags, possibly empty
+ */
+ @OptIn(ExperimentalUnsignedTypes::class)
+ private fun readTags(): ULongArray {
+ val tags = mutableListOf()
+
+ // Read tags (major type 6) until we encounter a non-tag
+ while ((parser.curByte shr 5) == 6) { // Major type 6: tag
+ val tag = parser.nextTag()
+ tags.add(tag)
+ }
+
+ return tags.toULongArray()
+ }
+
+
+ private fun readArray(tags: ULongArray): CborList {
+ val size = parser.startArray()
+ val elements = mutableListOf()
+
+ if (size >= 0) {
+ // Definite length array
+ repeat(size) {
+ elements.add(read())
+ }
+ } else {
+ // Indefinite length array
+ while (!parser.isEnd()) {
+ elements.add(read())
+ }
+ parser.end()
+ }
+
+ return CborList(elements, tags = tags)
+ }
+
+ private fun readMap(tags: ULongArray): CborMap {
+ val size = parser.startMap()
+ val elements = mutableMapOf()
+
+ if (size >= 0) {
+ // Definite length map
+ repeat(size) {
+ val key = read()
+ val value = read()
+ elements[key] = value
+ }
+ } else {
+ // Indefinite length map
+ while (!parser.isEnd()) {
+ val key = read()
+ val value = read()
+ elements[key] = value
+ }
+ parser.end()
+ }
+
+ return CborMap(elements, tags = tags)
+ }
+}
diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Decoder.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Decoder.kt
index 88075db26f..30584e93c1 100644
--- a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Decoder.kt
+++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Decoder.kt
@@ -12,9 +12,16 @@ import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.modules.*
-internal open class CborReader(override val cbor: Cbor, protected val parser: CborParser) : AbstractDecoder(),
+internal open class CborReader(override val cbor: Cbor, protected val parser: CborParserInterface) : AbstractDecoder(),
CborDecoder {
+ override fun decodeCborElement(): CborElement =
+ when (parser) {
+ is CborParser -> CborTreeReader(cbor.configuration, parser).read()
+ is StructuredCborParser -> parser.layer.current
+ }
+
+
protected var size = -1
private set
protected var finiteMode = false
@@ -51,7 +58,7 @@ internal open class CborReader(override val cbor: Cbor, protected val parser: Cb
}
override fun endStructure(descriptor: SerialDescriptor) {
- if (!finiteMode) parser.end()
+ if (!finiteMode || parser is StructuredCborParser) parser.end()
}
override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
@@ -109,7 +116,13 @@ internal open class CborReader(override val cbor: Cbor, protected val parser: Cb
@OptIn(ExperimentalSerializationApi::class)
override fun decodeSerializableValue(deserializer: DeserializationStrategy): T {
- return if ((decodeByteArrayAsByteString || cbor.configuration.alwaysUseByteString)
+ @Suppress("UNCHECKED_CAST")
+ return if (deserializer is CborSerializer) {
+ val tags = parser.processTags(tags)
+ decodeCborElement().also { /*this is a NOOP for structured parser but not from bytes */it.tags =
+ tags ?: ulongArrayOf()
+ } as T
+ } else if ((decodeByteArrayAsByteString || cbor.configuration.alwaysUseByteString)
&& deserializer.descriptor == ByteArraySerializer().descriptor
) {
@Suppress("UNCHECKED_CAST")
@@ -151,14 +164,15 @@ internal open class CborReader(override val cbor: Cbor, protected val parser: Cb
}
}
-internal class CborParser(private val input: ByteArrayInput, private val verifyObjectTags: Boolean) {
- private var curByte: Int = -1
+internal class CborParser(private val input: ByteArrayInput, private val verifyObjectTags: Boolean) :
+ CborParserInterface {
+ var curByte: Int = -1
init {
readByte()
}
- private fun readByte(): Int {
+ fun readByte(): Int {
curByte = input.read()
return curByte
}
@@ -170,9 +184,34 @@ internal class CborParser(private val input: ByteArrayInput, private val verifyO
readByte()
}
- fun isNull() = (curByte == NULL || curByte == EMPTY_MAP)
+ override fun isNull() = (curByte == NULL || curByte == EMPTY_MAP || curByte == -1)
- fun nextNull(tags: ULongArray? = null): Nothing? {
+ private fun readUnsignedValueFromAdditionalInfo(additionalInfo: Int): Long {
+ return when (additionalInfo) {
+ in 0..23 -> additionalInfo.toLong()
+ 24 -> {
+ val nextByte = readByte()
+ if (nextByte == -1) throw CborDecodingException("Unexpected EOF")
+ nextByte.toLong() and 0xFF
+ }
+
+ 25 -> input.readExact(2)
+ 26 -> input.readExact(4)
+ 27 -> input.readExact(8)
+ else -> throw CborDecodingException("Invalid additional info: $additionalInfo")
+ }
+ }
+
+ internal fun nextTag(): ULong {
+ if ((curByte shr 5) != 6) {
+ throw CborDecodingException("Expected tag (major type 6), got major type ${curByte shr 5}")
+ }
+
+ val additionalInfo = curByte and 0x1F
+ return readUnsignedValueFromAdditionalInfo(additionalInfo).toULong().also { skipByte(curByte) }
+ }
+
+ override fun nextNull(tags: ULongArray?): Nothing? {
processTags(tags)
if (curByte == NULL) {
skipByte(NULL)
@@ -182,7 +221,7 @@ internal class CborParser(private val input: ByteArrayInput, private val verifyO
return null
}
- fun nextBoolean(tags: ULongArray? = null): Boolean {
+ override fun nextBoolean(tags: ULongArray?): Boolean {
processTags(tags)
val ans = when (curByte) {
TRUE -> true
@@ -193,9 +232,9 @@ internal class CborParser(private val input: ByteArrayInput, private val verifyO
return ans
}
- fun startArray(tags: ULongArray? = null) = startSized(tags, BEGIN_ARRAY, HEADER_ARRAY, "array")
+ override fun startArray(tags: ULongArray?) = startSized(tags, BEGIN_ARRAY, HEADER_ARRAY, "array")
- fun startMap(tags: ULongArray? = null) = startSized(tags, BEGIN_MAP, HEADER_MAP, "map")
+ override fun startMap(tags: ULongArray?) = startSized(tags, BEGIN_MAP, HEADER_MAP, "map")
private fun startSized(
tags: ULongArray?,
@@ -215,11 +254,11 @@ internal class CborParser(private val input: ByteArrayInput, private val verifyO
return size
}
- fun isEnd() = curByte == BREAK
+ override fun isEnd() = curByte == BREAK
- fun end() = skipByte(BREAK)
+ override fun end() = skipByte(BREAK)
- fun nextByteString(tags: ULongArray? = null): ByteArray {
+ override fun nextByteString(tags: ULongArray?): ByteArray {
processTags(tags)
if ((curByte and 0b111_00000) != HEADER_BYTE_STRING)
throw CborDecodingException("start of byte string", curByte)
@@ -228,7 +267,7 @@ internal class CborParser(private val input: ByteArrayInput, private val verifyO
return arr
}
- fun nextString(tags: ULongArray? = null) = nextTaggedString(tags).first
+ override fun nextString(tags: ULongArray?) = nextTaggedString(tags).first
//used for reading the tag names and names of tagged keys (of maps, and serialized classes)
private fun nextTaggedString(tags: ULongArray?): Pair {
@@ -250,7 +289,7 @@ internal class CborParser(private val input: ByteArrayInput, private val verifyO
input.readExactNBytes(strLen)
}
- private fun processTags(tags: ULongArray?): ULongArray? {
+ override fun processTags(tags: ULongArray?): ULongArray? {
var index = 0
val collectedTags = mutableListOf()
while ((curByte and 0b111_00000) == HEADER_TAG) {
@@ -282,7 +321,7 @@ internal class CborParser(private val input: ByteArrayInput, private val verifyO
}
}
- internal fun verifyTagsAndThrow(expected: ULongArray, actual: ULongArray?) {
+ override fun verifyTagsAndThrow(expected: ULongArray, actual: ULongArray?) {
if (!expected.contentEquals(actual))
throw CborDecodingException(
"CBOR tags ${actual?.contentToString()} do not match expected tags ${expected.contentToString()}"
@@ -292,7 +331,7 @@ internal class CborParser(private val input: ByteArrayInput, private val verifyO
/**
* Used for reading the tags and either string (element name) or number (serial label)
*/
- fun nextTaggedStringOrNumber(): Triple {
+ override fun nextTaggedStringOrNumber(): Triple {
val collectedTags = processTags(null)
if ((curByte and 0b111_00000) == HEADER_STRING) {
val arr = readBytes()
@@ -306,7 +345,8 @@ internal class CborParser(private val input: ByteArrayInput, private val verifyO
}
}
- fun nextNumber(tags: ULongArray? = null): Long {
+
+ override fun nextNumber(tags: ULongArray?): Long {
processTags(tags)
val res = readNumber()
readByte()
@@ -314,22 +354,11 @@ internal class CborParser(private val input: ByteArrayInput, private val verifyO
}
private fun readNumber(): Long {
- val value = curByte and 0b000_11111
+ val additionalInfo = curByte and 0b000_11111
val negative = (curByte and 0b111_00000) == HEADER_NEGATIVE.toInt()
- val bytesToRead = when (value) {
- 24 -> 1
- 25 -> 2
- 26 -> 4
- 27 -> 8
- else -> 0
- }
- if (bytesToRead == 0) {
- return if (negative) -(value + 1).toLong()
- else value.toLong()
- }
- val res = input.readExact(bytesToRead)
- return if (negative) -(res + 1)
- else res
+
+ val value = readUnsignedValueFromAdditionalInfo(additionalInfo)
+ return if (negative) -(value + 1) else value
}
private fun ByteArrayInput.readExact(bytes: Int): Long {
@@ -350,7 +379,7 @@ internal class CborParser(private val input: ByteArrayInput, private val verifyO
return array
}
- fun nextFloat(tags: ULongArray? = null): Float {
+ override fun nextFloat(tags: ULongArray?): Float {
processTags(tags)
val res = when (curByte) {
NEXT_FLOAT -> Float.fromBits(readInt())
@@ -361,7 +390,7 @@ internal class CborParser(private val input: ByteArrayInput, private val verifyO
return res
}
- fun nextDouble(tags: ULongArray? = null): Double {
+ override fun nextDouble(tags: ULongArray?): Double {
processTags(tags)
val res = when (curByte) {
NEXT_DOUBLE -> Double.fromBits(readLong())
@@ -409,7 +438,7 @@ internal class CborParser(private val input: ByteArrayInput, private val verifyO
* been skipped, the "length stack" is [pruned][prune]. For indefinite length elements, a special marker is added to
* the "length stack" which is only popped from the "length stack" when a CBOR [break][isEnd] is encountered.
*/
- fun skipElement(tags: ULongArray?) {
+ override fun skipElement(tags: ULongArray?) {
val lengthStack = mutableListOf()
processTags(tags)
@@ -531,14 +560,205 @@ private fun Iterable.flatten(): ByteArray {
return output
}
+/**
+ * Iterator that keeps a reference to the current element and allows peeking at the next element.
+ * Works for single elements (where current is directly set to the element) and for collections (where current
+ * will be first set after `startMap` or `startArray`
+ */
+internal class PeekingIterator private constructor(
+ internal val isStructure: Boolean,
+ private val iter: ListIterator
+) : Iterator by iter {
+
+ lateinit var current: CborElement
+ private set
+
+ override fun next(): CborElement = iter.next().also { current = it }
+
+ fun peek() = if (hasNext()) {
+ val next = iter.next()
+ iter.previous()
+ next
+ } else null
+
+ companion object {
+ operator fun invoke(single: CborElement): PeekingIterator =
+ PeekingIterator(false, listOf(single).listIterator()).also { it.next() }
+
+ operator fun invoke(iter: ListIterator): PeekingIterator =
+ PeekingIterator(true, iter)
+ }
+}
+
+/**
+ * CBOR parser that operates on [CborElement] instead of bytes. Closely mirrors the behaviour of [CborParser], so the
+ * [CborDecoder] can remain largely unchanged.
+ */
+internal class StructuredCborParser(internal val element: CborElement, private val verifyObjectTags: Boolean) :
+ CborParserInterface {
+
+ internal var layer: PeekingIterator = PeekingIterator(element)
+ private set
+
+
+ private val layerStack = ArrayDeque()
+
+ // map needs special treatment because keys and values are laid out as a list alternating between key and value to
+ // mirror the byte-layout of a cbor map.
+ override fun isNull() =
+ if (layer.isStructure) layer.peek().let {
+ it is CborNull ||
+ /*THIS IS NOT CBOR-COMPLIANT but KxS-proprietary handling of nullable classes*/
+ (it is CborMap && it.isEmpty())
+ } else layer.current is CborNull
+
+ override fun isEnd() = !layer.hasNext()
+
+ override fun end() {
+ // Reset iterators when ending a structure
+ layer = layerStack.removeLast()
+ }
+
+ override fun startArray(tags: ULongArray?): Int {
+ processTags(tags)
+ if (layer.current !is CborList) {
+ throw CborDecodingException("Expected array, got ${layer.current::class.simpleName}")
+ }
+ layerStack += layer
+ val list = layer.current as CborList
+ layer = PeekingIterator(list.listIterator())
+ return list.size //we could just return -1 and let the current layer run out of elements to never run into inconsistencies
+ // if we do keep it like this, any inconsistencies serve as a canary for implementation bugs
+ }
+
+ override fun startMap(tags: ULongArray?): Int {
+ processTags(tags)
+ if (layer.current !is CborMap) {
+ throw CborDecodingException("Expected map, got ${layer.current::class.simpleName}")
+ }
+ layerStack += layer
+
+ val map = layer.current as CborMap
+ // zip key, value, key, value, ... pairs to mirror byte-layout of CBOR map, so decoding this here works the same
+ // as decoding from bytes
+ layer = PeekingIterator(map.entries.flatMap { listOf(it.key, it.value) }.listIterator())
+ return map.size//we could just return -1 and let the current layer run out of elements to never run into inconsistencies
+ // if we do keep it like this, any inconsistencies serve as a canary for implementation bugs
+ }
+
+ override fun nextNull(tags: ULongArray?): Nothing? {
+ processTags(tags)
+ if (layer.current !is CborNull) {
+ /* THIS IS NOT CBOR-COMPLIANT but KxS-proprietary handling of nullable classes*/
+ if (layer.current is CborMap && (layer.current as CborMap).isEmpty())
+ return null
+ throw CborDecodingException("Expected null, got ${layer.current::class.simpleName}")
+ }
+ return null
+ }
+
+ override fun nextBoolean(tags: ULongArray?): Boolean {
+ processTags(tags)
+ if (layer.current !is CborBoolean) {
+ throw CborDecodingException("Expected boolean, got ${layer.current::class.simpleName}")
+ }
+ return (layer.current as CborBoolean).value
+ }
+
+ override fun nextNumber(tags: ULongArray?): Long {
+ processTags(tags)
+ return when (layer.current) {
+ is CborPositiveInt -> (layer.current as CborPositiveInt).value.toLong()
+ is CborNegativeInt -> (layer.current as CborNegativeInt).value
+ else -> throw CborDecodingException("Expected number, got ${layer.current::class.simpleName}")
+ }
+ }
+
+ override fun nextString(tags: ULongArray?): String {
+ processTags(tags)
+ if (layer.current !is CborString) {
+ throw CborDecodingException("Expected string, got ${layer.current::class.simpleName}")
+ }
+ return (layer.current as CborString).value
+ }
+
+ override fun nextByteString(tags: ULongArray?): ByteArray {
+ processTags(tags)
+ if (layer.current !is CborByteString) {
+ throw CborDecodingException("Expected byte string, got ${layer.current::class.simpleName}")
+ }
+ return (layer.current as CborByteString).value
+ }
+
+ override fun nextDouble(tags: ULongArray?): Double {
+ processTags(tags)
+ return when (layer.current) {
+ is CborFloat -> (layer.current as CborFloat).value
+ else -> throw CborDecodingException("Expected double, got ${layer.current::class.simpleName}")
+ }
+ }
+
+ override fun nextFloat(tags: ULongArray?): Float {
+ return nextDouble(tags).toFloat()
+ }
+
+ override fun nextTaggedStringOrNumber(): Triple {
+ val tags = processTags(null)
+
+ return when (val key = layer.current) {
+ is CborString -> Triple(key.value, null, tags)
+ is CborPositiveInt -> Triple(null, key.value.toLong(), tags)
+ is CborNegativeInt -> Triple(null, key.value, tags)
+ else -> throw CborDecodingException("Expected string or number key, got ${key?.let { it::class.simpleName } ?: "null"}")
+ }
+ }
+
+ /**
+ * Verify the current element's object tags and advance to the next element if inside a list/map.
+ * The reason this method mixes two behaviours is that decoding a primitive is invoked on a single element.
+ * `decodeElementIndex`, etc. is invoked on an iterable and there are key tags and value tags
+ */
+ override fun processTags(tags: ULongArray?): ULongArray? {
+
+ // If we're in a list/map, advance to the next element
+ if (layer.hasNext()) layer.next()
+ // if we're at a primitive, we only process tags
+
+ // Store collected tags for verification
+ val collectedTags = if (layer.current.tags.isEmpty()) null else layer.current.tags
+
+ // Verify tags if needed
+ if (verifyObjectTags) {
+ tags?.let {
+ verifyTagsAndThrow(it, collectedTags)
+ }
+ }
+
+ return collectedTags
+ }
+
+ override fun verifyTagsAndThrow(expected: ULongArray, actual: ULongArray?) {
+ if (!expected.contentEquals(actual)) {
+ throw CborDecodingException(
+ "CBOR tags ${actual?.contentToString()} do not match expected tags ${expected.contentToString()}"
+ )
+ }
+ }
+
+ override fun skipElement(tags: ULongArray?) {
+ // Process tags but don't do anything with the element
+ processTags(tags)
+ }
+}
+
-private class CborMapReader(cbor: Cbor, decoder: CborParser) : CborListReader(cbor, decoder) {
+private class CborMapReader(cbor: Cbor, decoder: CborParserInterface) : CborListReader(cbor, decoder) {
override fun skipBeginToken(objectTags: ULongArray?) =
setSize(parser.startMap(tags?.let { if (objectTags == null) it else ulongArrayOf(*it, *objectTags) }
?: objectTags) * 2)
}
-private open class CborListReader(cbor: Cbor, decoder: CborParser) : CborReader(cbor, decoder) {
+private open class CborListReader(cbor: Cbor, decoder: CborParserInterface) : CborReader(cbor, decoder) {
private var ind = 0
override fun skipBeginToken(objectTags: ULongArray?) =
@@ -553,7 +773,6 @@ private open class CborListReader(cbor: Cbor, decoder: CborParser) : CborReader(
}
}
-
private val normalizeBaseBits = SINGLE_PRECISION_NORMALIZE_BASE.toBits()
@@ -617,4 +836,4 @@ private fun SerialDescriptor.getElementIndexOrThrow(name: String): Int {
" You can enable 'CborBuilder.ignoreUnknownKeys' property to ignore unknown keys"
)
return index
-}
+}
\ No newline at end of file
diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Encoder.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Encoder.kt
index eb5fc556a2..202b5fa674 100644
--- a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Encoder.kt
+++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Encoder.kt
@@ -26,8 +26,12 @@ private fun Stack.peek() = last()
// Split implementation to optimize base case
internal sealed class CborWriter(
override val cbor: Cbor,
- protected val output: ByteArrayOutput,
) : AbstractEncoder(), CborEncoder {
+
+ internal open fun encodeByteString(byteArray: ByteArray) {
+ getDestination().encodeByteString(byteArray)
+ }
+
protected var isClass = false
protected var encodeByteArrayAsByteString = false
@@ -46,7 +50,7 @@ internal sealed class CborWriter(
if ((encodeByteArrayAsByteString || cbor.configuration.alwaysUseByteString)
&& serializer.descriptor == ByteArraySerializer().descriptor
) {
- getDestination().encodeByteString(value as ByteArray)
+ encodeByteString(value as ByteArray)
} else {
encodeByteArrayAsByteString = encodeByteArrayAsByteString || serializer.descriptor.isInlineByteString()
super.encodeSerializableValue(serializer, value)
@@ -143,12 +147,14 @@ internal sealed class CborWriter(
incrementChildren() // needed for definite len encoding, NOOP for indefinite length encoding
return true
}
+
+ internal abstract fun encodeTags(tags: ULongArray)
}
// optimized indefinite length encoder
-internal class IndefiniteLengthCborWriter(cbor: Cbor, output: ByteArrayOutput) : CborWriter(
- cbor, output
+internal class IndefiniteLengthCborWriter(cbor: Cbor, private val output: ByteArrayOutput) : CborWriter(
+ cbor
) {
override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder {
@@ -177,10 +183,198 @@ internal class IndefiniteLengthCborWriter(cbor: Cbor, output: ByteArrayOutput) :
override fun incrementChildren() {/*NOOP*/
}
+ override fun encodeTags(tags: ULongArray) = tags.forEach { getDestination().encodeTag(it) }
+
+}
+
+// optimized indefinite length encoder
+internal class StructuredCborWriter(cbor: Cbor) : CborWriter(cbor) {
+
+ /**
+ * Tags and values are "written", i.e. recorded/prepared for encoding separately. Hence, we need a helper that allows
+ * for setting tags and values independently, and then merging them into the final [CborElement] at the end.
+ */
+ internal sealed class CborContainer(tags: ULongArray, elements: MutableList) {
+ protected val elements = elements
+
+ var tags = tags
+ internal set
+
+
+ open fun add(element: CborElement) = elements.add(element)
+ class Map(tags: ULongArray, elements: MutableList = mutableListOf()) :
+ CborContainer(tags, elements)
+
+ class List(tags: ULongArray, elements: MutableList = mutableListOf()) :
+ CborContainer(tags, elements)
+
+ class Primitive(tags: ULongArray) : CborContainer(tags, elements = mutableListOf()) {
+ override fun add(element: CborElement): Boolean {
+ require(elements.isEmpty()) { "Implementation error. Please report a bug." }
+ return elements.add(element)
+ }
+ }
+
+ fun finalize() = when (this) {
+ is List -> CborList(content = elements, tags = tags)
+ is Map -> CborMap(
+ content = if (elements.isNotEmpty()) IntRange(0, elements.size / 2 - 1).associate {
+ elements[it * 2] to elements[it * 2 + 1]
+ } else mapOf(),
+ tags = tags
+ )
+
+ is Primitive -> elements.first().also { it.tags += tags }
+
+ }
+ }
+
+ private operator fun CborContainer?.plusAssign(element: CborElement) {
+ this!!.add(element)
+ }
+
+
+ private val stack = ArrayDeque()
+ private var currentElement: CborContainer? = null
+
+ // value tags are collects inside beginStructure, so we need to cache them here and write them in beginStructure or encodeXXX
+ // and then null them out, so there are no leftovers
+ private var nextValueTags: ULongArray = ulongArrayOf()
+ get() {
+ val ret = field
+ field = ulongArrayOf()
+ return ret
+ }
+
+ fun finalize() = currentElement!!.finalize()
+
+ override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder {
+ val tags = nextValueTags +
+ if (cbor.configuration.encodeObjectTags) descriptor.getObjectTags() ?: ulongArrayOf()
+ else ulongArrayOf()
+ val element = if (descriptor.hasArrayTag()) {
+ CborContainer.List(tags)
+ } else {
+ when (descriptor.kind) {
+ StructureKind.LIST, is PolymorphicKind -> CborContainer.List(tags)
+ is StructureKind.MAP -> CborContainer.Map(tags)
+ else -> CborContainer.Map(tags)
+ }
+ }
+ currentElement?.let { stack.add(it) }
+ currentElement = element
+ return this
+ }
+
+ override fun endStructure(descriptor: SerialDescriptor) {
+ val finalized = currentElement!!.finalize()
+ if (stack.isNotEmpty()) {
+ currentElement = stack.removeLast()
+ currentElement += finalized
+ }
+ }
+
+ override fun getDestination() = throw IllegalStateException("There is no byteArrayInput")
+
+ override fun incrementChildren() {
+ /*NOOP*/
+ }
+
+
+ override fun encodeElement(descriptor: SerialDescriptor, index: Int): Boolean {
+ // this mirrors the special encoding of nullable classes that are null into am empty map.
+ // THIS IS NOT CBOR-COMPLiANT
+ // but keeps backwards compatibility with the way kotlinx.serialization CBOR format has always worked.
+ isClass = descriptor.getElementDescriptor(index).kind == StructureKind.CLASS
+
+ encodeByteArrayAsByteString = descriptor.isByteString(index)
+ //TODO check if cborelement and be done
+ val name = descriptor.getElementName(index)
+ if (!descriptor.hasArrayTag()) {
+ val keyTags = if (cbor.configuration.encodeKeyTags) descriptor.getKeyTags(index) else null
+
+ if ((descriptor.kind !is StructureKind.LIST) && (descriptor.kind !is StructureKind.MAP) && (descriptor.kind !is PolymorphicKind)) {
+ //indices are put into the name field. we don't want to write those, as it would result in double writes
+ val cborLabel = descriptor.getCborLabel(index)
+ if (cbor.configuration.preferCborLabelsOverNames && cborLabel != null) {
+ currentElement += CborInt(value = cborLabel, tags = keyTags ?: ulongArrayOf())
+ } else {
+ currentElement += CborString(name, tags = keyTags ?: ulongArrayOf())
+ }
+ }
+ }
+
+ if (cbor.configuration.encodeValueTags) {
+ descriptor.getValueTags(index).let { valueTags ->
+ //collect them for late encoding in beginStructure or encodeXXX
+ nextValueTags = valueTags ?: ulongArrayOf()
+ }
+ }
+ return true
+ }
+
+
+ override fun encodeTags(tags: ULongArray) {
+ nextValueTags += tags
+ }
+
+ override fun encodeBoolean(value: Boolean) {
+ currentElement += CborBoolean(value, tags = nextValueTags)
+ }
+
+ override fun encodeByte(value: Byte) {
+ currentElement += CborInt(value.toLong(), tags = nextValueTags)
+ }
+
+ override fun encodeChar(value: Char) {
+ currentElement += CborInt(value.code.toLong(), tags = nextValueTags)
+ }
+
+ override fun encodeDouble(value: Double) {
+ currentElement += CborFloat(value, tags = nextValueTags)
+ }
+
+ override fun encodeFloat(value: Float) {
+ currentElement += CborFloat(value.toDouble(), tags = nextValueTags)
+ }
+
+ override fun encodeInt(value: Int) {
+ currentElement += CborInt(value.toLong(), tags = nextValueTags)
+ }
+
+ override fun encodeLong(value: Long) {
+ currentElement += CborInt(value, tags = nextValueTags)
+ }
+
+ override fun encodeShort(value: Short) {
+ currentElement += CborInt(value.toLong(), tags = nextValueTags)
+ }
+
+ override fun encodeString(value: String) {
+ currentElement += CborString(value, tags = nextValueTags)
+ }
+
+ override fun encodeByteString(byteArray: ByteArray) {
+ currentElement += CborByteString(byteArray, tags = nextValueTags)
+ }
+
+ override fun encodeNull() {
+ /*NOT CBOR-COMPLIANT, KxS-proprietary behaviour*/
+ currentElement += if (isClass) CborMap(
+ mapOf(),
+ tags = nextValueTags
+ )
+ else CborNull(tags = nextValueTags)
+ }
+
+ override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) {
+ currentElement += CborString(enumDescriptor.getElementName(index), tags = nextValueTags)
+ }
+
}
//optimized definite length encoder
-internal class DefiniteLengthCborWriter(cbor: Cbor, output: ByteArrayOutput) : CborWriter(cbor, output) {
+internal class DefiniteLengthCborWriter(cbor: Cbor, output: ByteArrayOutput) : CborWriter(cbor) {
private val structureStack = Stack(Data(output, -1))
override fun getDestination(): ByteArrayOutput =
@@ -191,6 +385,8 @@ internal class DefiniteLengthCborWriter(cbor: Cbor, output: ByteArrayOutput) : C
structureStack.peek().elementCount++
}
+ override fun encodeTags(tags: ULongArray) = tags.forEach { getDestination().encodeTag(it) }
+
override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder {
val current = Data(ByteArrayOutput(), 0)
structureStack.push(current)
@@ -234,7 +430,7 @@ private fun ByteArrayOutput.startMap(size: ULong) {
composePositiveInline(size, HEADER_MAP)
}
-private fun ByteArrayOutput.encodeTag(tag: ULong) {
+internal fun ByteArrayOutput.encodeTag(tag: ULong) {
composePositiveInline(tag, HEADER_TAG)
}
@@ -329,4 +525,3 @@ private fun composeNegative(value: Long): ByteArray {
data[0] = data[0] or HEADER_NEGATIVE
return data
}
-
diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborArrayTest.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborArrayTest.kt
index c0b859566b..effe128e51 100644
--- a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborArrayTest.kt
+++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborArrayTest.kt
@@ -18,6 +18,10 @@ class CborArrayTest {
val cbor = Cbor.CoseCompliant
assertEquals(referenceHexString, cbor.encodeToHexString(ClassAs1Array.serializer(), reference))
assertEquals(reference, cbor.decodeFromHexString(ClassAs1Array.serializer(), referenceHexString))
+
+ val struct = cbor.encodeToCborElement(ClassAs1Array.serializer(), reference)
+ assertEquals(reference, cbor.decodeFromCborElement(ClassAs1Array.serializer(), struct))
+ assertEquals(referenceHexString, cbor.encodeToHexString(CborElement.serializer(), struct))
}
@Test
@@ -35,6 +39,10 @@ class CborArrayTest {
val cbor = Cbor.CoseCompliant
assertEquals(referenceHexString, cbor.encodeToHexString(ClassAs2Array.serializer(), reference))
assertEquals(reference, cbor.decodeFromHexString(ClassAs2Array.serializer(), referenceHexString))
+
+ val struct = cbor.encodeToCborElement(ClassAs2Array.serializer(), reference)
+ assertEquals(reference, cbor.decodeFromCborElement(ClassAs2Array.serializer(), struct))
+ assertEquals(referenceHexString, cbor.encodeToHexString(CborElement.serializer(), struct))
}
@Test
@@ -54,6 +62,10 @@ class CborArrayTest {
assertEquals(referenceHexString, cbor.encodeToHexString(ClassAs4ArrayNullable.serializer(), reference))
assertEquals(reference, cbor.decodeFromHexString(ClassAs4ArrayNullable.serializer(), referenceHexString))
+
+ val struct = cbor.encodeToCborElement(ClassAs4ArrayNullable.serializer(), reference)
+ assertEquals(reference, cbor.decodeFromCborElement(ClassAs4ArrayNullable.serializer(), struct))
+ assertEquals(referenceHexString, cbor.encodeToHexString(CborElement.serializer(), struct))
}
@Test
@@ -75,12 +87,10 @@ class CborArrayTest {
assertEquals(referenceHexString, cbor.encodeToHexString(ClassWithArray.serializer(), reference))
assertEquals(reference, cbor.decodeFromHexString(ClassWithArray.serializer(), referenceHexString))
- println(
- cbor.encodeToHexString(
- DoubleTaggedClassWithArray.serializer(),
- DoubleTaggedClassWithArray(array = ClassAs2Array(alg = -7, kid = "bar"))
- )
- )
+
+ val struct = cbor.encodeToCborElement(ClassWithArray.serializer(), reference)
+ assertEquals(reference, cbor.decodeFromCborElement(ClassWithArray.serializer(), struct))
+ assertEquals(referenceHexString, cbor.encodeToHexString(CborElement.serializer(), struct))
}
@@ -103,6 +113,12 @@ class CborArrayTest {
val cbor = Cbor.CoseCompliant
assertEquals(referenceHexString, cbor.encodeToHexString(DoubleTaggedClassWithArray.serializer(), reference))
assertEquals(reference, cbor.decodeFromHexString(DoubleTaggedClassWithArray.serializer(), referenceHexString))
+
+ val struct = cbor.encodeToCborElement(DoubleTaggedClassWithArray.serializer(), reference)
+ val structFromHex = cbor.decodeFromHexString(CborElement.serializer(), referenceHexString)
+ assertEquals(structFromHex, struct)
+ assertEquals(reference, cbor.decodeFromCborElement(DoubleTaggedClassWithArray.serializer(), struct))
+ assertEquals(referenceHexString, cbor.encodeToHexString(CborElement.serializer(), struct))
}
@CborArray
diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborDecoderTest.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborDecoderTest.kt
index 92aee674be..f100375aa6 100644
--- a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborDecoderTest.kt
+++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborDecoderTest.kt
@@ -17,7 +17,14 @@ class CborDecoderTest {
@Test
fun testDecodeSimpleObject() {
- assertEquals(Simple("str"), Cbor.decodeFromHexString(Simple.serializer(), "bf616163737472ff"))
+ val hex = "bf616163737472ff"
+ val reference = Simple("str")
+ assertEquals(reference, Cbor.decodeFromHexString(Simple.serializer(), hex))
+
+ val struct = Cbor.decodeFromHexString(hex)
+ assertEquals(reference, Cbor.decodeFromCborElement(Simple.serializer(), struct))
+
+ assertEquals(hex, Cbor.encodeToHexString(CborElement.serializer(), struct))
}
@Test
@@ -33,20 +40,40 @@ class CborDecoderTest {
HexConverter.parseHexBinary("cafe"),
HexConverter.parseHexBinary("cafe")
)
- // with maps, lists & strings of indefinite length
+ // with maps, lists & strings of indefinite length (note: this test vector did not correspond to proper encoding before, but decoded fine)
+ // this collapsing bytes wrapped in a bytes string into a byte string could be an indicator of a buggy (as in: too lenient) decoder.
+ val hex =
+ "bf637374726d48656c6c6f2c20776f726c64216169182a686e756c6c61626c65f6646c6973749f61616162ff636d6170bf01f502f4ff65696e6e6572bf6161636c6f6cff6a696e6e6572734c6973749fbf6161636b656bffff6a62797465537472696e6742cafe696279746541727261799f383521ffff"
assertEquals(
test, Cbor.decodeFromHexString(
TypesUmbrella.serializer(),
- "bf637374726d48656c6c6f2c20776f726c64216169182a686e756c6c61626c65f6646c6973749f61616162ff636d6170bf01f502f4ff65696e6e6572bf6161636c6f6cff6a696e6e6572734c6973749fbf6161636b656bffff6a62797465537472696e675f42cafeff696279746541727261799f383521ffff"
+ hex
)
)
+
+ val struct = Cbor.decodeFromHexString(hex)
+ assertEquals(Cbor.encodeToCborElement(test), struct)
+ assertEquals(test, Cbor.decodeFromCborElement(TypesUmbrella.serializer(), struct))
+
+ assertEquals(hex, Cbor.encodeToHexString(TypesUmbrella.serializer(), test))
+ assertEquals(hex, Cbor.encodeToHexString(CborElement.serializer(), struct))
+
+
+
// with maps, lists & strings of definite length
+ val hexDef =
+ "a9646c6973748261616162686e756c6c61626c65f6636d6170a202f401f56169182a6a696e6e6572734c69737481a16161636b656b637374726d48656c6c6f2c20776f726c642165696e6e6572a16161636c6f6c6a62797465537472696e6742cafe6962797465417272617982383521"
assertEquals(
test, Cbor.decodeFromHexString(
TypesUmbrella.serializer(),
- "a9646c6973748261616162686e756c6c61626c65f6636d6170a202f401f56169182a6a696e6e6572734c69737481a16161636b656b637374726d48656c6c6f2c20776f726c642165696e6e6572a16161636c6f6c6a62797465537472696e6742cafe6962797465417272617982383521"
+ hexDef
)
)
+
+ val structDef = Cbor.decodeFromHexString(hexDef)
+ assertEquals(Cbor.encodeToCborElement(test), structDef)
+ assertEquals(test, Cbor.decodeFromCborElement(TypesUmbrella.serializer(), structDef))
+
}
@Test
@@ -57,31 +84,43 @@ class CborDecoderTest {
* 44 # bytes(4)
* 01020304 # "\x01\x02\x03\x04"
*/
+ val hex = "a16a62797465537472696e674401020304"
+ val expected = NullableByteString(byteArrayOf(1, 2, 3, 4))
assertEquals(
- expected = NullableByteString(byteArrayOf(1, 2, 3, 4)),
+ expected = expected,
actual = Cbor.decodeFromHexString(
deserializer = NullableByteString.serializer(),
- hex = "a16a62797465537472696e674401020304"
+ hex = hex
)
)
+ val struct = Cbor.decodeFromHexString(hex)
+ assertEquals(expected, Cbor.decodeFromCborElement(NullableByteString.serializer(), struct))
+
/* A1 # map(1)
* 6A # text(10)
* 62797465537472696E67 # "byteString"
* F6 # primitive(22)
*/
+ val hexNull = "a16a62797465537472696e67f6"
+ val expectedNull = NullableByteString(byteString = null)
assertEquals(
- expected = NullableByteString(byteString = null),
+ expected = expectedNull,
actual = Cbor.decodeFromHexString(
deserializer = NullableByteString.serializer(),
- hex = "a16a62797465537472696e67f6"
+ hex = hexNull
)
)
+
+ val structNull = Cbor.decodeFromHexString(hexNull)
+ assertEquals(expectedNull, Cbor.decodeFromCborElement(NullableByteString.serializer(), structNull))
}
@Test
fun testNullables() {
Cbor.decodeFromHexString("a0")
+ val struct = Cbor.decodeFromHexString("a0")
+ assertEquals(NullableByteStringDefaultNull(), Cbor.decodeFromCborElement(NullableByteStringDefaultNull.serializer(), struct))
}
/**
@@ -91,18 +130,39 @@ class CborDecoderTest {
@Test
fun testIgnoreUnknownKeysFailsWhenCborDataIsMissingKeysThatArePresentInKotlinClass() {
// with maps & lists of indefinite length
+ val hex =
+ "bf637374726d48656c6c6f2c20776f726c64216169182a686e756c6c61626c65f6646c6973749f61616162ff636d6170bf01f502f4ff65696e6e6572bf6161636c6f6cff6a696e6e6572734c6973749fbf6161636b656bffffff"
+
assertFailsWithMessage("Field 'a' is required") {
ignoreUnknownKeys.decodeFromHexString(
Simple.serializer(),
- "bf637374726d48656c6c6f2c20776f726c64216169182a686e756c6c61626c65f6646c6973749f61616162ff636d6170bf01f502f4ff65696e6e6572bf6161636c6f6cff6a696e6e6572734c6973749fbf6161636b656bffffff"
+ hex
+ )
+ }
+
+ val struct = Cbor.decodeFromHexString(hex)
+ assertFailsWithMessage("Field 'a' is required") {
+ ignoreUnknownKeys.decodeFromCborElement(
+ Simple.serializer(),
+ struct
)
}
// with maps & lists of definite length
+ val hexDef =
+ "a7646c6973748261616162686e756c6c61626c65f6636d6170a202f401f56169182a6a696e6e6572734c69737481a16161636b656b637374726d48656c6c6f2c20776f726c642165696e6e6572a16161636c6f6c"
assertFailsWithMessage("Field 'a' is required") {
ignoreUnknownKeys.decodeFromHexString(
Simple.serializer(),
- "a7646c6973748261616162686e756c6c61626c65f6636d6170a202f401f56169182a6a696e6e6572734c69737481a16161636b656b637374726d48656c6c6f2c20776f726c642165696e6e6572a16161636c6f6c"
+ hexDef
+ )
+ }
+
+ val structDef = Cbor.decodeFromHexString(hexDef)
+ assertFailsWithMessage("Field 'a' is required") {
+ ignoreUnknownKeys.decodeFromCborElement(
+ Simple.serializer(),
+ structDef
)
}
}
@@ -121,13 +181,19 @@ class CborDecoderTest {
* 69676E6F7265 # "ignore"
* (missing value associated with "ignore" key)
*/
+ val hex = "a36373747266737472696e676169006669676e6f7265"
assertFailsWithMessage("Unexpected EOF while skipping element") {
ignoreUnknownKeys.decodeFromHexString(
TypesUmbrella.serializer(),
- "a36373747266737472696e676169006669676e6f7265"
+ hex
)
}
+
+ assertFailsWithMessage("Unexpected EOF") {
+ Cbor.decodeFromHexString(hex)
+ }
+
/* A3 # map(3)
* 63 # text(3)
* 737472 # "str"
@@ -141,12 +207,17 @@ class CborDecoderTest {
* A2 # map(2)
* (missing map contents associated with "ignore" key)
*/
+ val hex2 = "a36373747266737472696e676169006669676e6f7265a2"
assertFailsWithMessage("Unexpected EOF while skipping element") {
ignoreUnknownKeys.decodeFromHexString(
TypesUmbrella.serializer(),
- "a36373747266737472696e676169006669676e6f7265a2"
+ hex2
)
}
+
+ assertFailsWithMessage("Unexpected EOF") {
+ Cbor.decodeFromHexString(hex2)
+ }
}
@Test
@@ -160,19 +231,26 @@ class CborDecoderTest {
* 69676E6F7265 # "ignore"
* FF # primitive(*)
*/
+ val hex = "a36373747266737472696e676669676e6f7265ff"
assertFailsWithMessage("Expected next data item, but found FF") {
ignoreUnknownKeys.decodeFromHexString(
TypesUmbrella.serializer(),
- "a36373747266737472696e676669676e6f7265ff"
+ hex
)
}
+
+ assertFailsWithMessage("Invalid simple value or float type: FF") {
+ Cbor.decodeFromHexString(hex)
+ }
}
@Test
fun testDecodeCborWithUnknownField() {
+ val hex = "bf616163313233616263393837ff"
+ val expected = Simple("123")
assertEquals(
- expected = Simple("123"),
+ expected = expected,
actual = ignoreUnknownKeys.decodeFromHexString(
deserializer = Simple.serializer(),
@@ -187,15 +265,20 @@ class CborDecoderTest {
* 393837 # "987"
* FF # primitive(*)
*/
- hex = "bf616163313233616263393837ff"
+ hex = hex
)
)
+ val struct = Cbor.decodeFromHexString(hex)
+ assertEquals(expected, ignoreUnknownKeys.decodeFromCborElement(Simple.serializer(), struct))
+
}
@Test
fun testDecodeCborWithUnknownNestedIndefiniteFields() {
+ val hex = "bf6161633132336162bf7f6178ffa161790aff61639f010203ffff"
+ val expected = Simple("123")
assertEquals(
- expected = Simple("123"),
+ expected = expected,
actual = ignoreUnknownKeys.decodeFromHexString(
deserializer = Simple.serializer(),
@@ -225,9 +308,12 @@ class CborDecoderTest {
* FF # primitive(*)
* FF # primitive(*)
*/
- hex = "bf6161633132336162bf7f6178ffa161790aff61639f010203ffff"
+ hex = hex
)
)
+
+ val struct = Cbor.decodeFromHexString(hex)
+ assertEquals(expected, ignoreUnknownKeys.decodeFromCborElement(Simple.serializer(), struct))
}
/**
@@ -308,70 +394,106 @@ class CborDecoderTest {
* FF # primitive(*)
*/
+ val expected = SealedBox(
+ listOf(
+ SubSealedA("a"),
+ SubSealedB(1)
+ )
+ )
+ val hex =
+ "bf6565787472618309080765626f7865649f9f782d6b6f746c696e782e73657269616c697a6174696f6e2e53696d706c655365616c65642e5375625365616c656441bf61736161646e657741bf617801617902ffffff9f782d6b6f746c696e782e73657269616c697a6174696f6e2e53696d706c655365616c65642e5375625365616c656442bf616901ffffffff"
assertEquals(
- expected = SealedBox(
- listOf(
- SubSealedA("a"),
- SubSealedB(1)
- )
- ),
+ expected = expected,
actual = ignoreUnknownKeys.decodeFromHexString(
SealedBox.serializer(),
- "bf6565787472618309080765626f7865649f9f782d6b6f746c696e782e73657269616c697a6174696f6e2e53696d706c655365616c65642e5375625365616c656441bf61736161646e657741bf617801617902ffffff9f782d6b6f746c696e782e73657269616c697a6174696f6e2e53696d706c655365616c65642e5375625365616c656442bf616901ffffffff"
+ hex
)
)
+ val struct = Cbor.decodeFromHexString(hex)
+ assertEquals(expected, ignoreUnknownKeys.decodeFromCborElement(SealedBox.serializer(), struct))
+
}
@Test
fun testReadCustomByteString() {
+ val expected = TypeWithCustomByteString(CustomByteString(0x11, 0x22, 0x33))
+ val hex = "bf617843112233ff"
assertEquals(
- expected = TypeWithCustomByteString(CustomByteString(0x11, 0x22, 0x33)),
- actual = Cbor.decodeFromHexString("bf617843112233ff")
+ expected = expected,
+ actual = Cbor.decodeFromHexString(hex)
)
+ val struct = Cbor.decodeFromHexString(hex)
+ assertEquals(expected, Cbor.decodeFromCborElement(TypeWithCustomByteString.serializer(), struct))
+
}
@Test
fun testReadNullableCustomByteString() {
+ val hex = "bf617843112233ff"
+ val expected = TypeWithNullableCustomByteString(CustomByteString(0x11, 0x22, 0x33))
assertEquals(
- expected = TypeWithNullableCustomByteString(CustomByteString(0x11, 0x22, 0x33)),
- actual = Cbor.decodeFromHexString("bf617843112233ff")
+ expected = expected,
+ actual = Cbor.decodeFromHexString(hex)
)
+ val struct = Cbor.decodeFromHexString(hex)
+ assertEquals(expected, Cbor.decodeFromCborElement(TypeWithNullableCustomByteString.serializer(), struct))
+
}
@Test
fun testReadNullCustomByteString() {
+ val hex = "bf6178f6ff"
+ val expected = TypeWithNullableCustomByteString(null)
assertEquals(
- expected = TypeWithNullableCustomByteString(null),
- actual = Cbor.decodeFromHexString("bf6178f6ff")
+ expected = expected,
+ actual = Cbor.decodeFromHexString(hex)
)
+ val struct = Cbor.decodeFromHexString(hex)
+ assertEquals(expected, Cbor.decodeFromCborElement(TypeWithNullableCustomByteString.serializer(), struct))
+
}
@Test
fun testReadValueClassWithByteString() {
+ val expected = byteArrayOf(0x11, 0x22, 0x33)
+ val hex = "43112233"
assertContentEquals(
- expected = byteArrayOf(0x11, 0x22, 0x33),
- actual = Cbor.decodeFromHexString("43112233").x
+ expected = expected,
+ actual = Cbor.decodeFromHexString(hex).x
)
+ val struct = Cbor.decodeFromHexString(hex)
+ assertContentEquals(expected, Cbor.decodeFromCborElement(ValueClassWithByteString.serializer(), struct).x)
+
}
@Test
fun testReadValueClassCustomByteString() {
+ val expected = ValueClassWithCustomByteString(CustomByteString(0x11, 0x22, 0x33))
+ val hex = "43112233"
assertEquals(
- expected = ValueClassWithCustomByteString(CustomByteString(0x11, 0x22, 0x33)),
- actual = Cbor.decodeFromHexString("43112233")
+ expected = expected,
+ actual = Cbor.decodeFromHexString(hex)
)
+ val struct = Cbor.decodeFromHexString(hex)
+ assertEquals(expected, Cbor.decodeFromCborElement(ValueClassWithCustomByteString.serializer(), struct))
+
}
@Test
fun testReadValueClassWithUnlabeledByteString() {
+ val expected = byteArrayOf(
+ 0x11,
+ 0x22,
+ 0x33
+ )
+ val hex = "43112233"
assertContentEquals(
- expected = byteArrayOf(
- 0x11,
- 0x22,
- 0x33
- ),
- actual = Cbor.decodeFromHexString("43112233").x.x
+ expected = expected,
+ actual = Cbor.decodeFromHexString(hex).x.x
)
+ val struct = Cbor.decodeFromHexString(hex)
+ assertContentEquals(expected, Cbor.decodeFromCborElement(ValueClassWithUnlabeledByteString.serializer(), struct).x.x)
+
}
}
diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborDefiniteLengthTest.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborDefiniteLengthTest.kt
index b19a409308..ab7425d73f 100644
--- a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborDefiniteLengthTest.kt
+++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborDefiniteLengthTest.kt
@@ -26,6 +26,15 @@ class CborDefiniteLengthTest {
"a9637374726d48656c6c6f2c20776f726c64216169182a686e756c6c61626c65f6646c6973748261616162636d6170a201f502f465696e6e6572a16161636c6f6c6a696e6e6572734c69737481a16161636b656b6a62797465537472696e6742cafe6962797465417272617982383521",
Cbor { useDefiniteLengthEncoding = true }.encodeToHexString(TypesUmbrella.serializer(), test)
)
+ assertEquals(
+ "a9637374726d48656c6c6f2c20776f726c64216169182a686e756c6c61626c65f6646c6973748261616162636d6170a201f502f465696e6e6572a16161636c6f6c6a696e6e6572734c69737481a16161636b656b6a62797465537472696e6742cafe6962797465417272617982383521",
+ Cbor { useDefiniteLengthEncoding = true }.run {
+ encodeToHexString(
+ CborElement.serializer(),
+ encodeToCborElement(TypesUmbrella.serializer(), test)
+ )
+ }
+ )
}
}
\ No newline at end of file
diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborElementEqualityTest.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborElementEqualityTest.kt
new file mode 100644
index 0000000000..40bf07f893
--- /dev/null
+++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborElementEqualityTest.kt
@@ -0,0 +1,319 @@
+/*
+ * Copyright 2017-2025 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.cbor
+
+import kotlin.test.*
+
+class CborElementEqualityTest {
+
+ @Test
+ fun testCborPositiveIntEquality() {
+ val int1 = CborPositiveInt(42u)
+ val int2 = CborPositiveInt(42u)
+ val int3 = CborPositiveInt(43u)
+ val int4 = CborPositiveInt(42u, 1u)
+
+ // Same values should be equal
+ assertEquals(int1, int2)
+ assertEquals(int1.hashCode(), int2.hashCode())
+
+ // Different values should not be equal
+ assertNotEquals(int1, int3)
+
+ // Different tags should not be equal
+ assertNotEquals(int1, int4)
+
+ // Null comparison
+ assertNotEquals(int1, null as CborElement?)
+
+ // Different type comparison
+ assertNotEquals(int1 as CborElement, CborString("42"))
+ assertNotEquals(int1, CborString("42") as CborElement)
+ }
+
+ @Test
+ fun testCborNegativeIntEquality() {
+ val int1 = CborNegativeInt(-42)
+ val int2 = CborNegativeInt(-42)
+ val int3 = CborNegativeInt(-43)
+ val int4 = CborNegativeInt(-42, 1u)
+
+ assertEquals(int1, int2)
+ assertEquals(int1.hashCode(), int2.hashCode())
+ assertNotEquals(int1, int3)
+ assertNotEquals(int1, int4)
+ assertNotEquals(int1, null as CborElement?)
+ assertNotEquals(int1, CborPositiveInt(42u) as CborElement)
+ assertNotEquals(int1 as CborElement, CborPositiveInt(42u))
+ }
+
+ @Test
+ fun testCborDoubleEquality() {
+ val double1 = CborFloat(3.14)
+ val double2 = CborFloat(3.14)
+ val double3 = CborFloat(2.71)
+ val double4 = CborFloat(3.14, 1u)
+
+ assertEquals(double1, double2)
+ assertEquals(double1.hashCode(), double2.hashCode())
+ assertNotEquals(double1, double3)
+ assertNotEquals(double1, double4)
+ assertNotEquals(double1, null as CborElement?)
+ assertNotEquals(double1 as CborElement, CborString("3.14"))
+ assertNotEquals(double1, CborString("3.14") as CborElement)
+ }
+
+ @Test
+ fun testCborStringEquality() {
+ val string1 = CborString("hello")
+ val string2 = CborString("hello")
+ val string3 = CborString("world")
+ val string4 = CborString("hello", 1u)
+
+ assertEquals(string1, string2)
+ assertEquals(string1.hashCode(), string2.hashCode())
+ assertNotEquals(string1, string3)
+ assertNotEquals(string1, string4)
+ assertNotEquals(string1, null as CborElement?)
+ assertNotEquals(string1 as CborElement, CborPositiveInt(123u))
+ assertNotEquals(string1, CborPositiveInt(123u) as CborElement)
+ }
+
+ @Test
+ fun testCborBooleanEquality() {
+ val bool1 = CborBoolean(true)
+ val bool2 = CborBoolean(true)
+ val bool3 = CborBoolean(false)
+ val bool4 = CborBoolean(true, 1u)
+
+ assertEquals(bool1, bool2)
+ assertEquals(bool1.hashCode(), bool2.hashCode())
+ assertNotEquals(bool1, bool3)
+ assertNotEquals(bool1, bool4)
+ assertNotEquals(bool1, null as CborElement?)
+ assertNotEquals(bool1 as CborElement, CborString("true"))
+ assertNotEquals(bool1, CborString("true") as CborElement)
+ }
+
+ @Test
+ fun testCborByteStringEquality() {
+ val bytes1 = byteArrayOf(1, 2, 3)
+ val bytes2 = byteArrayOf(1, 2, 3)
+ val bytes3 = byteArrayOf(4, 5, 6)
+
+ val byteString1 = CborByteString(bytes1)
+ val byteString2 = CborByteString(bytes2)
+ val byteString3 = CborByteString(bytes3)
+ val byteString4 = CborByteString(bytes1, 1u)
+
+ assertEquals(byteString1, byteString2)
+ assertEquals(byteString1.hashCode(), byteString2.hashCode())
+ assertNotEquals(byteString1, byteString3)
+ assertNotEquals(byteString1, byteString4)
+ assertNotEquals(byteString1, null as CborElement?)
+ assertNotEquals(byteString1 as CborElement, CborString("123"))
+ assertNotEquals(byteString1, CborString("123") as CborElement)
+ }
+
+ @Test
+ fun testCborNullEquality() {
+ val null1 = CborNull()
+ val null2 = CborNull()
+ val null3 = CborNull(1u)
+
+ assertEquals(null1, null2)
+ assertEquals(null1.hashCode(), null2.hashCode())
+ assertNotEquals(null1, null3)
+ assertNotEquals(null1, null as CborElement?)
+ assertNotEquals(null1 as CborElement, CborString("null"))
+ assertNotEquals(null1, CborString("null") as CborElement)
+ }
+
+ @Test
+ fun testCborListEquality() {
+ val list1 = CborList(listOf(CborPositiveInt(1u), CborString("test")))
+ val list2 = CborList(listOf(CborPositiveInt(1u), CborString("test")))
+ val list3 = CborList(listOf(CborPositiveInt(2u), CborString("test")))
+ val list4 = CborList(listOf(CborPositiveInt(1u), CborString("test")), 1u)
+ val list5 = CborList(listOf(CborPositiveInt(1u)))
+
+ assertEquals(list1, list2)
+ assertEquals(list1.hashCode(), list2.hashCode())
+ assertNotEquals(list1, list3)
+ assertNotEquals(list1, list4)
+ assertNotEquals(list1, list5)
+ assertNotEquals(list1, null as CborElement?)
+ assertNotEquals(list1 as CborElement, CborString("list"))
+ assertNotEquals(list1, CborString("list") as CborElement)
+ }
+
+ @Test
+ fun testCborMapEquality() {
+ val map1 = CborMap(
+ mapOf(
+ CborString("key1") to CborPositiveInt(1u),
+ CborString("key2") to CborString("value")
+ )
+ )
+ val map2 = CborMap(
+ mapOf(
+ CborString("key1") to CborPositiveInt(1u),
+ CborString("key2") to CborString("value")
+ )
+ )
+ val map3 = CborMap(
+ mapOf(
+ CborString("key1") to CborPositiveInt(2u),
+ CborString("key2") to CborString("value")
+ )
+ )
+ val map4 = CborMap(
+ mapOf(
+ CborString("key1") to CborPositiveInt(1u),
+ CborString("key2") to CborString("value")
+ ), 1u
+ )
+ val map5 = CborMap(
+ mapOf(
+ CborString("key1") to CborPositiveInt(1u)
+ )
+ )
+
+ assertEquals(map1, map2)
+ assertEquals(map1.hashCode(), map2.hashCode())
+ assertNotEquals(map1, map3)
+ assertNotEquals(map1, map4)
+ assertNotEquals(map1, map5)
+ assertNotEquals(map1, null as CborElement?)
+ assertNotEquals(map1 as CborElement, CborString("map"))
+ assertNotEquals(map1, CborString("map") as CborElement)
+ }
+
+ @Test
+ fun testTagsEquality() {
+ val tags1 = ulongArrayOf(1u, 2u, 3u)
+ val tags2 = ulongArrayOf(1u, 2u, 3u)
+ val tags3 = ulongArrayOf(1u, 2u, 4u)
+
+ val string1 = CborString("test", tags = tags1)
+ val string2 = CborString("test", tags = tags2)
+ val string3 = CborString("test", tags = tags3)
+
+ assertEquals(string1, string2)
+ assertEquals(string1.hashCode(), string2.hashCode())
+ assertNotEquals(string1, string3)
+ }
+
+ @Test
+ fun testEmptyCollectionsEquality() {
+ val emptyList1 = CborList(emptyList())
+ val emptyList2 = CborList(emptyList())
+ val emptyMap1 = CborMap(emptyMap())
+ val emptyMap2 = CborMap(emptyMap())
+
+ assertEquals(emptyList1, emptyList2)
+ assertEquals(emptyList1.hashCode(), emptyList2.hashCode())
+ assertEquals(emptyMap1, emptyMap2)
+ assertEquals(emptyMap1.hashCode(), emptyMap2.hashCode())
+ assertNotEquals(emptyList1 as CborElement, emptyMap1)
+ assertNotEquals(emptyList1, emptyMap1 as CborElement)
+ }
+
+ @Test
+ fun testNestedStructureEquality() {
+ val nested1 = CborMap(
+ mapOf(
+ CborString("list") to CborList(
+ listOf(
+ CborPositiveInt(1u),
+ CborMap(mapOf(CborString("inner") to CborNull()))
+ )
+ )
+ )
+ )
+ val nested2 = CborMap(
+ mapOf(
+ CborString("list") to CborList(
+ listOf(
+ CborPositiveInt(1u),
+ CborMap(mapOf(CborString("inner") to CborNull()))
+ )
+ )
+ )
+ )
+ val nested3 = CborMap(
+ mapOf(
+ CborString("list") to CborList(
+ listOf(
+ CborPositiveInt(2u),
+ CborMap(mapOf(CborString("inner") to CborNull()))
+ )
+ )
+ )
+ )
+
+ assertEquals(nested1, nested2)
+ assertEquals(nested1.hashCode(), nested2.hashCode())
+ assertNotEquals(nested1, nested3)
+ }
+
+ @Test
+ fun testReflexiveEquality() {
+ val elements = listOf(
+ CborPositiveInt(42u),
+ CborNegativeInt(-42),
+ CborFloat(3.14),
+ CborString("test"),
+ CborBoolean(true),
+ CborByteString(byteArrayOf(1, 2, 3)),
+ CborNull(),
+ CborList(listOf(CborPositiveInt(1u))),
+ CborMap(mapOf(CborString("key") to CborPositiveInt(1u)))
+ )
+
+ elements.forEach { element ->
+ assertEquals(element, element, "Element should be equal to itself")
+ assertEquals(element.hashCode(), element.hashCode(), "Hash code should be consistent")
+ }
+ }
+
+ @Test
+ fun testSymmetricEquality() {
+ val pairs = listOf(
+ CborPositiveInt(42u) to CborPositiveInt(42u),
+ CborNegativeInt(-42) to CborNegativeInt(-42),
+ CborFloat(3.14) to CborFloat(3.14),
+ CborString("test") to CborString("test"),
+ CborBoolean(true) to CborBoolean(true),
+ CborByteString(byteArrayOf(1, 2, 3)) to CborByteString(byteArrayOf(1, 2, 3)),
+ CborNull() to CborNull(),
+ CborList(listOf(CborPositiveInt(1u))) to CborList(listOf(CborPositiveInt(1u))),
+ CborMap(mapOf(CborString("key") to CborPositiveInt(1u))) to CborMap(
+ mapOf(
+ CborString("key") to CborPositiveInt(
+ 1u
+ )
+ )
+ )
+ )
+
+ pairs.forEach { (a, b) ->
+ assertEquals(a, b, "a should equal b")
+ assertEquals(b, a, "b should equal a (symmetry)")
+ assertEquals(a.hashCode(), b.hashCode(), "Hash codes should be equal")
+ }
+ }
+
+ @Test
+ fun testTransitiveEquality() {
+ val a = CborString("test")
+ val b = CborString("test")
+ val c = CborString("test")
+
+ assertEquals(a, b)
+ assertEquals(b, c)
+ assertEquals(a, c, "Transitivity: if a==b and b==c, then a==c")
+ }
+}
\ No newline at end of file
diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborElementTest.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborElementTest.kt
new file mode 100644
index 0000000000..102c5f911e
--- /dev/null
+++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborElementTest.kt
@@ -0,0 +1,632 @@
+/*
+ * Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.cbor
+
+import kotlinx.serialization.*
+import kotlinx.serialization.cbor.internal.*
+import kotlin.test.*
+
+class CborElementTest {
+
+ private val cbor = Cbor {}
+
+ @Test
+ fun testCborNull() {
+ val nullElement = CborNull()
+ val nullBytes = cbor.encodeToByteArray(nullElement)
+ val decodedNull = cbor.decodeFromByteArray(nullBytes)
+ assertEquals(nullElement, decodedNull)
+ }
+
+ @Test
+ fun testCborNumber() {
+ val numberElement = CborPositiveInt(42u)
+ val numberBytes = cbor.encodeToByteArray(numberElement)
+ val decodedNumber = cbor.decodeFromByteArray(numberBytes)
+ assertEquals(numberElement, decodedNumber)
+ assertEquals(42u, (decodedNumber as CborPositiveInt).value)
+ }
+
+ @Test
+ fun testCborString() {
+ val stringElement = CborString("Hello, CBOR!")
+ val stringBytes = cbor.encodeToByteArray(stringElement)
+ val decodedString = cbor.decodeFromByteArray(stringBytes)
+ assertEquals(stringElement, decodedString)
+ assertEquals("Hello, CBOR!", (decodedString as CborString).value)
+ }
+
+ @Test
+ fun testCborBoolean() {
+ val booleanElement = CborBoolean(true)
+ val booleanBytes = cbor.encodeToByteArray(booleanElement)
+ val decodedBoolean = cbor.decodeFromByteArray(booleanBytes)
+ assertEquals(booleanElement, decodedBoolean)
+ assertEquals(true, (decodedBoolean as CborBoolean).value)
+ }
+
+ @Test
+ fun testCborByteString() {
+ val byteArray = byteArrayOf(1, 2, 3, 4, 5)
+ val byteStringElement = CborByteString(byteArray)
+ val byteStringBytes = cbor.encodeToByteArray(byteStringElement)
+ val decodedByteString = cbor.decodeFromByteArray(byteStringBytes)
+ assertEquals(byteStringElement, decodedByteString)
+ assertTrue((decodedByteString as CborByteString).value.contentEquals(byteArray))
+ }
+
+ @Test
+ fun testCborList() {
+ val listElement = CborList(
+ listOf(
+ CborPositiveInt(1u),
+ CborString("two"),
+ CborBoolean(true),
+ CborNull()
+ )
+ )
+ val listBytes = cbor.encodeToByteArray(listElement)
+ val decodedList = cbor.decodeFromByteArray(listBytes)
+
+ // Verify the type and size
+ assertTrue(decodedList is CborList)
+ assertEquals(4, decodedList.size)
+
+ // Verify individual elements
+ assertTrue(decodedList[0] is CborPositiveInt)
+ assertEquals(1u, (decodedList[0] as CborPositiveInt).value)
+
+ assertTrue(decodedList[1] is CborString)
+ assertEquals("two", (decodedList[1] as CborString).value)
+
+ assertTrue(decodedList[2] is CborBoolean)
+ assertEquals(true, (decodedList[2] as CborBoolean).value)
+
+ assertTrue(decodedList[3] is CborNull)
+ }
+
+ @Test
+ fun testCborMap() {
+ val mapElement = CborMap(
+ mapOf(
+ CborString("key1") to CborPositiveInt(42u),
+ CborString("key2") to CborString("value"),
+ CborPositiveInt(3u) to CborBoolean(true),
+ CborNull() to CborNull()
+ )
+ )
+ val mapBytes = cbor.encodeToByteArray(mapElement)
+ val decodedMap = cbor.decodeFromByteArray(mapBytes)
+
+ // Verify the type and size
+ assertTrue(decodedMap is CborMap)
+ assertEquals(4, decodedMap.size)
+
+ // Verify individual entries
+ assertTrue(decodedMap.containsKey(CborString("key1")))
+ val value1 = decodedMap[CborString("key1")]
+ assertTrue(value1 is CborPositiveInt)
+ assertEquals(42u, (value1 as CborPositiveInt).value)
+
+ assertTrue(decodedMap.containsKey(CborString("key2")))
+ val value2 = decodedMap[CborString("key2")]
+ assertTrue(value2 is CborString)
+ assertEquals("value", (value2 as CborString).value)
+
+ assertTrue(decodedMap.containsKey(CborPositiveInt(3u)))
+ val value3 = decodedMap[CborPositiveInt(3u)]
+ assertTrue(value3 is CborBoolean)
+ assertEquals(true, (value3 as CborBoolean).value)
+
+ assertTrue(decodedMap.containsKey(CborNull()))
+ val value4 = decodedMap[CborNull()]
+ assertTrue(value4 is CborNull)
+ }
+
+ @Test
+ fun testComplexNestedStructure() {
+ // Create a complex nested structure with maps and lists
+ val complexElement = CborMap(
+ mapOf(
+ CborString("primitives") to CborList(
+ listOf(
+ CborPositiveInt(123u),
+ CborString("text"),
+ CborBoolean(false),
+ CborByteString(byteArrayOf(10, 20, 30)),
+ CborNull()
+ )
+ ),
+ CborString("nested") to CborMap(
+ mapOf(
+ CborString("inner") to CborList(
+ listOf(
+ CborPositiveInt(1u),
+ CborPositiveInt(2u)
+ )
+ ),
+ CborString("empty") to CborList(emptyList())
+ )
+ )
+ )
+ )
+
+ val complexBytes = cbor.encodeToByteArray(complexElement)
+ val decodedComplex = cbor.decodeFromByteArray(complexBytes)
+
+ // Verify the type
+ assertTrue(decodedComplex is CborMap)
+
+ // Verify the primitives list
+ assertTrue(decodedComplex.containsKey(CborString("primitives")))
+ val primitivesValue = decodedComplex[CborString("primitives")]
+ assertTrue(primitivesValue is CborList)
+
+ assertEquals(5, primitivesValue.size)
+
+ assertTrue(primitivesValue[0] is CborPositiveInt)
+ assertEquals(123u, (primitivesValue[0] as CborPositiveInt).value)
+
+ assertTrue(primitivesValue[1] is CborString)
+ assertEquals("text", (primitivesValue[1] as CborString).value)
+
+ assertTrue(primitivesValue[2] is CborBoolean)
+ assertEquals(false, (primitivesValue[2] as CborBoolean).value)
+
+ assertTrue(primitivesValue[3] is CborByteString)
+ assertTrue((primitivesValue[3] as CborByteString).value.contentEquals(byteArrayOf(10, 20, 30)))
+
+ assertTrue(primitivesValue[4] is CborNull)
+
+ // Verify the nested map
+ assertTrue(decodedComplex.containsKey(CborString("nested")))
+ val nestedValue = decodedComplex[CborString("nested")]
+ assertTrue(nestedValue is CborMap)
+
+ assertEquals(2, nestedValue.size)
+
+ // Verify the inner list
+ assertTrue(nestedValue.containsKey(CborString("inner")))
+ val innerValue = nestedValue[CborString("inner")]
+ assertTrue(innerValue is CborList)
+
+ assertEquals(2, innerValue.size)
+
+ assertTrue(innerValue[0] is CborPositiveInt)
+ assertEquals(1u, (innerValue[0] as CborPositiveInt).value)
+
+ assertTrue(innerValue[1] is CborPositiveInt)
+ assertEquals(2u, (innerValue[1] as CborPositiveInt).value)
+
+ // Verify the empty list
+ assertTrue(nestedValue.containsKey(CborString("empty")))
+ val emptyValue = nestedValue[CborString("empty")]
+ assertTrue(emptyValue is CborList)
+ val empty = emptyValue
+
+ assertEquals(0, empty.size)
+ }
+
+ @Test
+ fun testDecodePositiveInt() {
+ // Test data from CborParserTest.testParseIntegers
+ val element = cbor.decodeFromHexString("0C") as CborPositiveInt
+ assertEquals(12u, element.value)
+ }
+
+ @Test
+ fun testDecodeStrings() {
+ // Test data from CborParserTest.testParseStrings
+ val element = cbor.decodeFromHexString("6568656C6C6F")
+ assertTrue(element is CborString)
+ assertEquals("hello", element.value)
+
+ val longStringElement =
+ cbor.decodeFromHexString("7828737472696E672074686174206973206C6F6E676572207468616E2032332063686172616374657273")
+ assertTrue(longStringElement is CborString)
+ assertEquals("string that is longer than 23 characters", longStringElement.value)
+ }
+
+ @Test
+ fun testDecodeFloatingPoint() {
+ // Test data from CborParserTest.testParseDoubles
+ val doubleElement = cbor.decodeFromHexString("fb7e37e43c8800759c")
+ assertTrue(doubleElement is CborFloat)
+ assertEquals(1e+300, doubleElement.value)
+
+ val floatElement = cbor.decodeFromHexString("fa47c35000")
+ assertTrue(floatElement is CborFloat)
+ assertEquals(100000.0f, floatElement.value.toFloat())
+ }
+
+ @Test
+ fun testDecodeByteString() {
+ // Test data from CborParserTest.testRfc7049IndefiniteByteStringExample
+ val element = cbor.decodeFromHexString("5F44aabbccdd43eeff99FF")
+ assertTrue(element is CborByteString)
+ val byteString = element as CborByteString
+ val expectedBytes = HexConverter.parseHexBinary("aabbccddeeff99")
+ assertTrue(byteString.value.contentEquals(expectedBytes))
+ }
+
+ @Test
+ fun testDecodeArray() {
+ // Test data from CborParserTest.testSkipCollections
+ val element = cbor.decodeFromHexString("830118ff1a00010000")
+ assertTrue(element is CborList)
+ val list = element as CborList
+ assertEquals(3, list.size)
+ assertEquals(1u, (list[0] as CborPositiveInt).value)
+ assertEquals(255u, (list[1] as CborPositiveInt).value)
+ assertEquals(65536u, (list[2] as CborPositiveInt).value)
+ }
+
+ @Test
+ fun testDecodeMap() {
+ // Test data from CborParserTest.testSkipCollections
+ val element = cbor.decodeFromHexString("a26178676b6f746c696e7861796d73657269616c697a6174696f6e")
+ assertTrue(element is CborMap)
+ val map = element as CborMap
+ assertEquals(2, map.size)
+ assertEquals(CborString("kotlinx"), map[CborString("x")])
+ assertEquals(CborString("serialization"), map[CborString("y")])
+ }
+
+ @Test
+ fun testDecodeComplexStructure() {
+ // Test data from CborParserTest.testSkipIndefiniteLength
+ val element =
+ cbor.decodeFromHexString("a461615f42cafe43010203ff61627f6648656c6c6f2065776f726c64ff61639f676b6f746c696e786d73657269616c697a6174696f6eff6164bf613101613202613303ff")
+ assertTrue(element is CborMap)
+ val map = element as CborMap
+ assertEquals(4, map.size)
+
+ // Check the byte string
+ val byteString = map[CborString("a")] as CborByteString
+ val expectedBytes = HexConverter.parseHexBinary("cafe010203")
+ assertTrue(byteString.value.contentEquals(expectedBytes))
+
+ // Check the text string
+ assertEquals(CborString("Hello world"), map[CborString("b")])
+
+ // Check the array
+ val array = map[CborString("c")] as CborList
+ assertEquals(2, array.size)
+ assertEquals(CborString("kotlinx"), array[0])
+ assertEquals(CborString("serialization"), array[1])
+
+ // Check the nested map
+ val nestedMap = map[CborString("d")] as CborMap
+ assertEquals(3, nestedMap.size)
+ assertEquals(CborPositiveInt(1u), nestedMap[CborString("1")])
+ assertEquals(CborPositiveInt(2u), nestedMap[CborString("2")])
+ assertEquals(CborPositiveInt(3u), nestedMap[CborString("3")])
+ }
+
+ @OptIn(ExperimentalStdlibApi::class)
+ @Test
+ fun testTagsRoundTrip() {
+ // Create a CborElement with tags
+ val originalElement = CborString("Hello, tagged world!", tags = ulongArrayOf(42u))
+
+ // Encode and decode
+ val bytes = cbor.encodeToByteArray(originalElement)
+ println(bytes.toHexString())
+ val decodedElement = cbor.decodeFromByteArray(bytes)
+
+ // Verify the value and tags
+ assertTrue(decodedElement is CborString)
+ assertEquals("Hello, tagged world!", decodedElement.value)
+ assertEquals(1, decodedElement.tags.size)
+ assertEquals(42u, decodedElement.tags.first())
+ }
+
+ @Test
+ fun testGenericAndCborSpecificMixed() {
+ Triple(
+ Cbor {
+ encodeValueTags = true
+ encodeKeyTags = true
+ verifyKeyTags = true
+ verifyObjectTags = true
+ verifyValueTags = true
+ },
+ MixedBag(
+ str = "A string, is a string, is a string",
+ bStr = CborByteString(byteArrayOf()),
+ cborElement = CborBoolean(false),
+ cborPositiveInt = CborPositiveInt(1u),
+ cborInt = CborInt(-1),
+ tagged = 26
+ ),
+ "bf6373747278224120737472696e672c206973206120737472696e672c206973206120737472696e676462537472406b63626f72456c656d656e74f46f63626f72506f736974697665496e74016763626f72496e7420d82a66746167676564d90921181aff"
+ )
+ .let { (cbor, obj, hex) ->
+ val struct = cbor.encodeToCborElement(obj)
+ assertEquals(hex, cbor.encodeToHexString(obj))
+ assertEquals(hex, cbor.encodeToHexString(struct))
+ assertEquals(struct, cbor.decodeFromHexString(hex))
+ assertEquals(obj, cbor.decodeFromCborElement(struct))
+ assertEquals(obj, cbor.decodeFromHexString(hex))
+ }
+
+ Triple(
+ Cbor {
+ encodeValueTags = true
+ encodeKeyTags = true
+ verifyKeyTags = true
+ verifyObjectTags = true
+ verifyValueTags = true
+ },
+ MixedBag(
+ str = "A string, is a string, is a string",
+ bStr = null,
+ cborElement = CborBoolean(false),
+ cborPositiveInt = CborPositiveInt(1u),
+ cborInt = CborInt(-1),
+ tagged = 26
+ ),
+ "bf6373747278224120737472696e672c206973206120737472696e672c206973206120737472696e676462537472f66b63626f72456c656d656e74f46f63626f72506f736974697665496e74016763626f72496e7420d82a66746167676564d90921181aff"
+ )
+ .let { (cbor, obj, hex) ->
+ val struct = cbor.encodeToCborElement(obj)
+ assertEquals(hex, cbor.encodeToHexString(obj))
+ assertEquals(hex, cbor.encodeToHexString(struct))
+ assertEquals(struct, cbor.decodeFromHexString(hex))
+ assertEquals(obj, cbor.decodeFromCborElement(struct))
+ assertEquals(obj, cbor.decodeFromHexString(hex))
+ }
+
+
+ Triple(
+ Cbor {
+ encodeValueTags = true
+ encodeKeyTags = true
+ verifyKeyTags = true
+ verifyObjectTags = true
+ verifyValueTags = true
+ },
+ MixedBag(
+ str = "A string, is a string, is a string",
+ bStr = null,
+ cborElement = CborMap(mapOf(CborByteString(byteArrayOf(1, 3, 3, 7)) to CborNull())),
+ cborPositiveInt = CborPositiveInt(1u),
+ cborInt = CborInt(-1),
+ tagged = 26
+ ),
+ "bf6373747278224120737472696e672c206973206120737472696e672c206973206120737472696e676462537472f66b63626f72456c656d656e74bf4401030307f6ff6f63626f72506f736974697665496e74016763626f72496e7420d82a66746167676564d90921181aff"
+ )
+ .let { (cbor, obj, hex) ->
+ val struct = cbor.encodeToCborElement(obj)
+ assertEquals(hex, cbor.encodeToHexString(obj))
+ assertEquals(hex, cbor.encodeToHexString(struct))
+ assertEquals(struct, cbor.decodeFromHexString(hex))
+ assertEquals(obj, cbor.decodeFromCborElement(struct))
+ assertEquals(obj, cbor.decodeFromHexString(hex))
+ }
+
+
+
+ Triple(
+ Cbor {
+ encodeValueTags = true
+ encodeKeyTags = true
+ verifyKeyTags = true
+ verifyObjectTags = true
+ verifyValueTags = true
+ },
+ MixedBag(
+ str = "A string, is a string, is a string",
+ bStr = null,
+ cborElement = CborNull(),
+ cborPositiveInt = CborPositiveInt(1u),
+ cborInt = CborInt(-1),
+ tagged = 26
+ ),
+ "bf6373747278224120737472696e672c206973206120737472696e672c206973206120737472696e676462537472f66b63626f72456c656d656e74f66f63626f72506f736974697665496e74016763626f72496e7420d82a66746167676564d90921181aff"
+ )
+ .let { (cbor, obj, hex) ->
+ val struct = cbor.encodeToCborElement(obj)
+ assertEquals(hex, cbor.encodeToHexString(obj))
+ assertEquals(hex, cbor.encodeToHexString(struct))
+ assertEquals(struct, cbor.decodeFromHexString(hex))
+ //we have an ambiguity here (null vs. CborNull), so we cannot compare for equality with the object
+ //assertEquals(obj, cbor.decodeFromCbor(struct))
+ //assertEquals(obj, cbor.decodeFromHexString(hex))
+ }
+
+ Triple(
+ Cbor {
+ encodeValueTags = true
+ encodeKeyTags = true
+ verifyKeyTags = true
+ verifyObjectTags = true
+ verifyValueTags = true
+ },
+ MixedBag(
+ str = "A string, is a string, is a string",
+ bStr = CborByteString(byteArrayOf(), 1u, 3u),
+ cborElement = CborBoolean(false),
+ cborPositiveInt = CborPositiveInt(1u),
+ cborInt = CborInt(-1),
+ tagged = 26
+ ),
+ "bf6373747278224120737472696e672c206973206120737472696e672c206973206120737472696e676462537472c1c3406b63626f72456c656d656e74f46f63626f72506f736974697665496e74016763626f72496e7420d82a66746167676564d90921181aff"
+ )
+ .let { (cbor, obj, hex) ->
+ val struct = cbor.encodeToCborElement(obj)
+ assertEquals(hex, cbor.encodeToHexString(obj))
+ assertEquals(hex, cbor.encodeToHexString(struct))
+ assertEquals(struct, cbor.decodeFromHexString(hex))
+ assertEquals(obj, cbor.decodeFromCborElement(struct))
+ assertEquals(obj, cbor.decodeFromHexString(hex))
+ }
+
+ Triple(
+ Cbor {
+ encodeValueTags = false
+ encodeKeyTags = true
+ verifyKeyTags = true
+ verifyObjectTags = true
+ verifyValueTags = false
+ },
+ MixedBag(
+ str = "A string, is a string, is a string",
+ bStr = CborByteString(byteArrayOf(), 1u, 3u),
+ cborElement = CborBoolean(false),
+ cborPositiveInt = CborPositiveInt(1u),
+ cborInt = CborInt(-1),
+ tagged = 26
+ ),
+ "bf6373747278224120737472696e672c206973206120737472696e672c206973206120737472696e676462537472c1c3406b63626f72456c656d656e74f46f63626f72506f736974697665496e74016763626f72496e7420d82a66746167676564181aff"
+ )
+ .let { (cbor, obj, hex) ->
+ val struct = cbor.encodeToCborElement(obj)
+ assertEquals(hex, cbor.encodeToHexString(obj))
+ assertEquals(hex, cbor.encodeToHexString(struct))
+ assertEquals(struct, cbor.decodeFromHexString