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(hex)) + assertEquals(obj, cbor.decodeFromCborElement(struct)) + assertEquals(obj, cbor.decodeFromHexString(hex)) + } + + Triple( + Cbor { + encodeValueTags = true + encodeKeyTags = true + verifyKeyTags = true + verifyObjectTags = true + verifyValueTags = true + }, + MixedTag( + cborElement = CborBoolean(false), + ), + "bfd82a6b63626f72456c656d656e74d90921f4ff" + ) + .let { (cbor, obj, hex) -> + val struct = cbor.encodeToCborElement(obj) + assertEquals(hex, cbor.encodeToHexString(obj)) + assertEquals(hex, cbor.encodeToHexString(struct)) + assertEquals(struct, cbor.decodeFromHexString(hex)) + // this is ambiguous. the cborBoolean has a tag attached in the struct, coming from the valueTag (as intended), + // so now the resulting object won't have a tag but the cborElement property will have a tag attached + // hence, the following two will have: + // Expected :MixedTag(cborElement=CborPrimitive(kind=Boolean, tags=, value=false)) + // Actual :MixedTag(cborElement=CborPrimitive(kind=Boolean, tags=2337, value=false)) + assertNotEquals(obj, cbor.decodeFromCborElement(struct)) + assertNotEquals(obj, cbor.decodeFromHexString(hex)) + } + Triple( + Cbor { + encodeValueTags = true + encodeKeyTags = true + verifyKeyTags = true + verifyObjectTags = true + verifyValueTags = true + }, + MixedTag( + cborElement = CborBoolean(false, 90u), + ), + //valueTags first, then CborElement tags + "bfd82a6b63626f72456c656d656e74d90921d85af4ff" + ) + .let { (cbor, obj, hex) -> + val struct = cbor.encodeToCborElement(obj) + assertEquals(hex, cbor.encodeToHexString(obj)) + assertEquals(hex, cbor.encodeToHexString(struct)) + assertEquals(struct, cbor.decodeFromHexString(hex)) + // this is ambiguous. the cborBoolean has a tag attached in the struct, coming from the valueTag (as intended), + // so now the resulting object won't have a tag but the cborElement property will have a tag attached + // hence, the following two will have: + // Expected :MixedTag(cborElement=CborPrimitive(kind=Boolean, tags=90, value=false)) + // Actual :MixedTag(cborElement=CborPrimitive(kind=Boolean, tags=2337, 90, value=false)) + //of course, the value tag verification will also fail hard + assertFailsWith( + CborDecodingException::class, + "CBOR tags [2337, 90] do not match expected tags [2337]" + ) { + assertNotEquals(obj, cbor.decodeFromCborElement(struct)) + } + assertFailsWith( + CborDecodingException::class, + "CBOR tags [2337, 90] do not match expected tags [2337]" + ) { + assertNotEquals(obj, cbor.decodeFromHexString(hex)) + } + } + + Triple( + Cbor { + encodeValueTags = true + encodeKeyTags = true + verifyKeyTags = true + verifyObjectTags = true + verifyValueTags = false + }, + MixedTag( + cborElement = CborBoolean(false, 90u), + ), + //valueTags first, then CborElement tags + "bfd82a6b63626f72456c656d656e74d90921d85af4ff" + ) + .let { (cbor, obj, hex) -> + val struct = cbor.encodeToCborElement(obj) + assertEquals(hex, cbor.encodeToHexString(obj)) + assertEquals(hex, cbor.encodeToHexString(struct)) + assertEquals(struct, cbor.decodeFromHexString(hex)) + // this is ambiguous. the cborBoolean has a tag attached in the struct, coming from the valueTag (as intended), + // so now the resulting object won't have a tag but the cborElement property will have a tag attached + // hence, the following two will have: + // Expected :MixedTag(cborElement=CborPrimitive(kind=Boolean, tags=90, value=false)) + // Actual :MixedTag(cborElement=CborPrimitive(kind=Boolean, tags=2337, 90, value=false)) + assertNotEquals(obj, cbor.decodeFromCborElement(struct)) + assertNotEquals(obj, cbor.decodeFromHexString(hex)) + } + + + Triple( + Cbor { + encodeValueTags = false + encodeKeyTags = true + verifyKeyTags = true + verifyObjectTags = true + verifyValueTags = false + }, + MixedTag( + cborElement = CborBoolean(false, 90u), + ), + "bfd82a6b63626f72456c656d656e74d85af4ff" + ) + .let { (cbor, obj, hex) -> + val struct = cbor.encodeToCborElement(obj) + assertEquals(hex, cbor.encodeToHexString(obj)) + assertEquals(hex, cbor.encodeToHexString(struct)) + assertEquals(struct, cbor.decodeFromHexString(hex)) + //no value tags means everything's fine again + assertEquals(obj, cbor.decodeFromCborElement(struct)) + assertEquals(obj, cbor.decodeFromHexString(hex)) + } + } + +} + +@Serializable +data class MixedBag( + val str: String, + val bStr: CborByteString?, + val cborElement: CborElement?, + val cborPositiveInt: CborPrimitive<*>, + val cborInt: CborInt<*>, + @KeyTags(42u) + @ValueTags(2337u) + val tagged: Int +) + + +@Serializable +data class MixedTag( + @KeyTags(42u) + @ValueTags(2337u) + val cborElement: CborElement?, +) diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborIsoTest.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborIsoTest.kt index 49ef3390ae..639d69a22a 100644 --- a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborIsoTest.kt +++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborIsoTest.kt @@ -27,6 +27,11 @@ class CborIsoTest { } assertEquals(reference, cbor.decodeFromHexString(DataClass.serializer(), referenceHexString)) assertEquals(referenceHexString, cbor.encodeToHexString(DataClass.serializer(), reference)) + + val struct = cbor.encodeToCborElement(DataClass.serializer(), reference) + assertEquals(struct, cbor.decodeFromHexString(CborElement.serializer(), referenceHexString)) + assertEquals(reference, cbor.decodeFromCborElement(DataClass.serializer(), struct)) + assertEquals(referenceHexString, cbor.encodeToHexString(CborElement.serializer(), struct)) } @Serializable diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborLabelTest.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborLabelTest.kt index 0ecb5283db..06b82a9d63 100644 --- a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborLabelTest.kt +++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborLabelTest.kt @@ -35,6 +35,10 @@ class CborLabelTest { } assertEquals(referenceHexLabelString, cbor.encodeToHexString(ClassWithCborLabel.serializer(), reference)) assertEquals(reference, cbor.decodeFromHexString(ClassWithCborLabel.serializer(), referenceHexLabelString)) + + val struct = cbor.encodeToCborElement(ClassWithCborLabel.serializer(), reference) + assertEquals(reference, cbor.decodeFromCborElement(ClassWithCborLabel.serializer(), struct)) + assertEquals(referenceHexLabelString, cbor.encodeToHexString(CborElement.serializer(), struct)) } @Test @@ -44,6 +48,11 @@ class CborLabelTest { } assertEquals(referenceHexNameString, cbor.encodeToHexString(ClassWithCborLabel.serializer(), reference)) assertEquals(reference, cbor.decodeFromHexString(ClassWithCborLabel.serializer(), referenceHexNameString)) + + + val struct = cbor.encodeToCborElement(ClassWithCborLabel.serializer(), reference) + assertEquals(reference, cbor.decodeFromCborElement(ClassWithCborLabel.serializer(), struct)) + assertEquals(referenceHexNameString, cbor.encodeToHexString(CborElement.serializer(), struct)) } @Test @@ -64,6 +73,10 @@ class CborLabelTest { } assertEquals(referenceHexLabelWithTagString, cbor.encodeToHexString(ClassWithCborLabelAndTag.serializer(), referenceWithTag)) assertEquals(referenceWithTag, cbor.decodeFromHexString(ClassWithCborLabelAndTag.serializer(), referenceHexLabelWithTagString)) + + val struct = cbor.encodeToCborElement(ClassWithCborLabelAndTag.serializer(), referenceWithTag) + assertEquals(referenceWithTag, cbor.decodeFromCborElement(ClassWithCborLabelAndTag.serializer(), struct)) + assertEquals(referenceHexLabelWithTagString, cbor.encodeToHexString(CborElement.serializer(), struct)) } @Test @@ -84,6 +97,15 @@ class CborLabelTest { assertFailsWith(CborDecodingException::class) { cbor.decodeFromHexString(ClassWithCborLabelAndTag.serializer(), referenceHexLabelWithTagString) } + + //we can encode and decode since it is a valid structure + val struct = cbor.decodeFromHexString(CborElement.serializer(), referenceHexLabelWithTagString) + assertEquals(referenceHexLabelWithTagString, cbor.encodeToHexString(CborElement.serializer(), struct)) + + //we cannot deserialize from the struct since it does not match the class structure + assertFailsWith(CborDecodingException::class) { + cbor.decodeFromCborElement(ClassWithCborLabelAndTag.serializer(), struct) + } } @Test @@ -107,6 +129,18 @@ class CborLabelTest { useDefiniteLengthEncoding = true } assertEquals(referenceWithTag, cbor.decodeFromHexString(ClassWithCborLabelAndTag.serializer(), referenceHexLabelWithTagString)) + + //no unknown props + val struct = cbor.encodeToCborElement(ClassWithCborLabelAndTag.serializer(), referenceWithTag) + + //with unknown props + val structFromString = cbor.decodeFromHexString(CborElement.serializer(), referenceHexLabelWithTagString) + //must obv mismatch + assertNotEquals(struct, structFromString) + assertNotEquals(referenceHexLabelWithTagString, cbor.encodeToHexString(CborElement.serializer(), struct)) + + assertEquals(referenceWithTag, cbor.decodeFromCborElement(ClassWithCborLabelAndTag.serializer(), struct)) + assertEquals(referenceHexLabelWithTagString, cbor.encodeToHexString(CborElement.serializer(), structFromString)) } @Test @@ -128,6 +162,11 @@ class CborLabelTest { } assertEquals(referenceWithoutLabel, cbor.decodeFromHexString(ClassWithoutCborLabel.serializer(), referenceHexStringWithoutLabel)) + + val struct = cbor.encodeToCborElement(ClassWithoutCborLabel.serializer(), referenceWithoutLabel) + assertEquals(referenceWithoutLabel, cbor.decodeFromCborElement(ClassWithoutCborLabel.serializer(), struct)) + assertEquals(referenceHexStringWithoutLabel, cbor.encodeToHexString(CborElement.serializer(), struct)) + } @Serializable diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborPolymorphismTest.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborPolymorphismTest.kt index 156f471480..e3a72e27ce 100644 --- a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborPolymorphismTest.kt +++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborPolymorphismTest.kt @@ -18,12 +18,20 @@ class CborPolymorphismTest { @Test fun testSealedWithOneSubclass() { + val original = A.B("bbb") + val hexResultToCheck = + "9f78336b6f746c696e782e73657269616c697a6174696f6e2e63626f722e43626f72506f6c796d6f72706869736d546573742e412e42bf616263626262ffff" assertSerializedToBinaryAndRestored( - A.B("bbb"), + original, A.serializer(), cbor, - hexResultToCheck = "9f78336b6f746c696e782e73657269616c697a6174696f6e2e63626f722e43626f72506f6c796d6f72706869736d546573742e412e42bf616263626262ffff" + hexResultToCheck = hexResultToCheck ) + + val struct = cbor.encodeToCborElement(A.serializer(), original) + assertEquals(struct, cbor.decodeFromHexString(CborElement.serializer(), hexResultToCheck)) + assertEquals(hexResultToCheck, cbor.encodeToHexString(struct)) + assertEquals(original, cbor.decodeFromCborElement(A.serializer(), struct)) } @Test @@ -35,6 +43,9 @@ class CborPolymorphismTest { ) ) assertSerializedToBinaryAndRestored(obj, SealedBox.serializer(), cbor) + + val struct = cbor.encodeToCborElement(SealedBox.serializer(), obj) + assertEquals(obj, cbor.decodeFromCborElement(SealedBox.serializer(), struct)) } @Test @@ -46,5 +57,9 @@ class CborPolymorphismTest { ) ) assertSerializedToBinaryAndRestored(obj, PolyBox.serializer(), cbor) + + + val struct = cbor.encodeToCborElement(PolyBox.serializer(), obj) + assertEquals(obj, cbor.decodeFromCborElement(PolyBox.serializer(), struct)) } } diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborTaggedTest.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborTaggedTest.kt index fa884c0ab4..28d9ddb398 100644 --- a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborTaggedTest.kt +++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborTaggedTest.kt @@ -223,7 +223,7 @@ class CborTaggedTest { @Test fun writeReadVerifyTaggedClass() { - assertEquals(referenceHexString, Cbor { + val cbor = Cbor { useDefiniteLengthEncoding = false encodeKeyTags = true encodeValueTags = true @@ -231,77 +231,119 @@ class CborTaggedTest { verifyKeyTags = true verifyValueTags = true verifyObjectTags = true - }.encodeToHexString(DataWithTags.serializer(), reference)) - assertEquals( - referenceHexStringDefLen, - Cbor { - useDefiniteLengthEncoding = true - encodeKeyTags = true - encodeValueTags = true - encodeObjectTags = true - verifyKeyTags = true - verifyValueTags = true - verifyObjectTags = true - }.encodeToHexString(DataWithTags.serializer(), reference) - ) + } + assertEquals(referenceHexString, cbor.encodeToHexString(DataWithTags.serializer(), reference)) + val structFromHex = cbor.decodeFromHexString(CborElement.serializer(), referenceHexString) + val struct = cbor.encodeToCborElement(DataWithTags.serializer(), reference) + assertEquals(struct, structFromHex) + assertEquals(reference, cbor.decodeFromCborElement(DataWithTags.serializer(), struct)) + assertEquals(referenceHexString, cbor.encodeToHexString(CborElement.serializer(), struct)) + + val cborDef = Cbor { + useDefiniteLengthEncoding = true + encodeKeyTags = true + encodeValueTags = true + encodeObjectTags = true + verifyKeyTags = true + verifyValueTags = true + verifyObjectTags = true + } + assertEquals(referenceHexStringDefLen, cborDef.encodeToHexString(DataWithTags.serializer(), reference)) + val structDefFromHex = cborDef.decodeFromHexString(CborElement.serializer(), referenceHexStringDefLen) + val structDef = cborDef.encodeToCborElement(DataWithTags.serializer(), reference) + assertEquals(structDef, structDefFromHex) + assertEquals(reference, cborDef.decodeFromCborElement(DataWithTags.serializer(), structDef)) + assertEquals(referenceHexStringDefLen, cborDef.encodeToHexString(CborElement.serializer(), structDef)) + + assertEquals(reference, Cbor.CoseCompliant.decodeFromHexString(DataWithTags.serializer(), referenceHexString)) + val structCoseFromHex = Cbor.CoseCompliant.decodeFromHexString(CborElement.serializer(), referenceHexString) + val structCose = Cbor.CoseCompliant.encodeToCborElement(DataWithTags.serializer(), reference) + assertEquals(structCose, structCoseFromHex) + assertEquals(reference, Cbor.CoseCompliant.decodeFromCborElement(DataWithTags.serializer(), structCose)) + assertEquals( reference, Cbor.CoseCompliant.decodeFromHexString(DataWithTags.serializer(), referenceHexStringDefLen) ) + val structCoseFromHexDef = + Cbor.CoseCompliant.decodeFromHexString(CborElement.serializer(), referenceHexStringDefLen) + val structCoseDef = Cbor.CoseCompliant.encodeToCborElement(DataWithTags.serializer(), reference) + assertEquals(structCoseDef, structCoseFromHexDef) + assertEquals(reference, Cbor.CoseCompliant.decodeFromCborElement(DataWithTags.serializer(), structCoseDef)) + } @Test fun writeReadUntaggedKeys() { - assertEquals(noKeyTags, Cbor { + val cborNoKeyTags = Cbor { encodeKeyTags = false encodeValueTags = true encodeObjectTags = true verifyKeyTags = false verifyValueTags = true verifyObjectTags = true - }.encodeToHexString(DataWithTags.serializer(), reference)) - assertEquals( - noKeyTagsDefLen, - Cbor { - useDefiniteLengthEncoding = true - encodeKeyTags = false - encodeValueTags = true - encodeObjectTags = true - verifyKeyTags = true - verifyValueTags = true - verifyObjectTags = true - }.encodeToHexString( - DataWithTags.serializer(), - reference - ) - ) - assertEquals(reference, Cbor { - encodeKeyTags = true - encodeValueTags = true - encodeObjectTags = true - verifyValueTags = true - verifyObjectTags = true - verifyKeyTags = false - }.decodeFromHexString(noKeyTags)) - assertEquals(reference, Cbor { - encodeKeyTags = true + } + assertEquals(noKeyTags, cborNoKeyTags.encodeToHexString(DataWithTags.serializer(), reference)) + (cborNoKeyTags to noKeyTags).let { (cbor, hex) -> + val struct = cbor.encodeToCborElement(DataWithTags.serializer(), reference) + assertEquals(struct, cbor.decodeFromHexString(CborElement.serializer(), hex)) + assertEquals(reference, cbor.decodeFromCborElement(DataWithTags.serializer(), struct)) + } + + val cborNoKeyTagsDefLen = Cbor { + useDefiniteLengthEncoding = true + encodeKeyTags = false encodeValueTags = true encodeObjectTags = true + verifyKeyTags = true verifyValueTags = true verifyObjectTags = true - verifyKeyTags = false - }.decodeFromHexString(noKeyTagsDefLen)) - assertEquals(reference, Cbor { + } + assertEquals(noKeyTagsDefLen, cborNoKeyTagsDefLen.encodeToHexString(DataWithTags.serializer(), reference)) + (cborNoKeyTagsDefLen to noKeyTagsDefLen).let { (cbor, hex) -> + val struct = cbor.encodeToCborElement(DataWithTags.serializer(), reference) + assertEquals(struct, cbor.decodeFromHexString(CborElement.serializer(), hex)) + // this must fail, because encoding/decoding is not symmetric with the current config (the struct does not have the tags, but the hex string does) + assertFailsWith(CborDecodingException::class) { + assertEquals(reference, cbor.decodeFromCborElement(DataWithTags.serializer(), struct)) + } + } + + val cborEncodingKeyTags = Cbor { encodeKeyTags = true encodeValueTags = true encodeObjectTags = true verifyValueTags = true verifyObjectTags = true verifyKeyTags = false - }.decodeFromHexString(referenceHexString)) + } + assertEquals(reference, cborEncodingKeyTags.decodeFromHexString(noKeyTags)) + (cborEncodingKeyTags to noKeyTags).let { (cbor, hex) -> + val struct = cbor.encodeToCborElement(DataWithTags.serializer(), reference) + // this must not be equal, because the scruct has the tags, but the hex string doesn't + assertNotEquals(struct, cbor.decodeFromHexString(CborElement.serializer(), hex)) + assertEquals(reference, cbor.decodeFromCborElement(DataWithTags.serializer(), struct)) + } + + assertEquals(reference, cborEncodingKeyTags.decodeFromHexString(noKeyTagsDefLen)) + (cborEncodingKeyTags to noKeyTagsDefLen).let { (cbor, hex) -> + val struct = cbor.encodeToCborElement(DataWithTags.serializer(), reference) + // this must not be equals, because the scruct has the tags, but the hex string doesn't (as above) + assertNotEquals(struct, cbor.decodeFromHexString(CborElement.serializer(), hex)) + assertEquals(reference, cbor.decodeFromCborElement(DataWithTags.serializer(), struct)) + } + + + assertEquals(reference, cborEncodingKeyTags.decodeFromHexString(referenceHexString)) + (cborNoKeyTags to noKeyTags).let { (cbor, hex) -> + val struct = cbor.encodeToCborElement(DataWithTags.serializer(), reference) + assertEquals(struct, cbor.decodeFromHexString(CborElement.serializer(), hex)) + assertEquals(reference, cbor.decodeFromCborElement(DataWithTags.serializer(), struct)) + } assertFailsWith(CborDecodingException::class) { + //Tested against struct inside one of the above let-blocks Cbor.CoseCompliant.decodeFromHexString( DataWithTags.serializer(), noKeyTags @@ -309,46 +351,55 @@ class CborTaggedTest { } assertFailsWith(CborDecodingException::class) { - Cbor { - encodeKeyTags = true - encodeValueTags = true - encodeObjectTags = true - verifyValueTags = true - verifyObjectTags = true - verifyKeyTags = false - }.decodeFromHexString(DataWithTags.serializer(), noValueTags) + //Tested against struct inside one of the above let-blocks + cborEncodingKeyTags.decodeFromHexString(DataWithTags.serializer(), noValueTags) } } @Test fun writeReadUntaggedValues() { - assertEquals( - noValueTags, - Cbor { - encodeKeyTags = true - encodeObjectTags = true - verifyKeyTags = true - verifyValueTags = true - verifyObjectTags = true - encodeValueTags = false - }.encodeToHexString(DataWithTags.serializer(), reference) - ) - assertEquals(reference, Cbor { + val cborNoValueTags = Cbor { encodeKeyTags = true - encodeValueTags = true encodeObjectTags = true verifyKeyTags = true + verifyValueTags = true verifyObjectTags = true - verifyValueTags = false - }.decodeFromHexString(noValueTags)) - assertEquals(reference, Cbor { + encodeValueTags = false + } + assertEquals(noValueTags, cborNoValueTags.encodeToHexString(DataWithTags.serializer(), reference)) + (cborNoValueTags to noValueTags).let { (cbor, hex) -> + val struct = cbor.encodeToCborElement(DataWithTags.serializer(), reference) + assertEquals(struct, cbor.decodeFromHexString(CborElement.serializer(), hex)) + // no value tags are written to the struct, so this will fail + assertFailsWith(CborDecodingException::class) { + assertEquals(reference, cbor.decodeFromCborElement(DataWithTags.serializer(), struct)) + } + } + + + val cborEncodingValueTags = Cbor { encodeKeyTags = true encodeValueTags = true encodeObjectTags = true verifyKeyTags = true verifyObjectTags = true verifyValueTags = false - }.decodeFromHexString(referenceHexString)) + } + assertEquals(reference, cborEncodingValueTags.decodeFromHexString(noValueTags)) + (cborEncodingValueTags to noValueTags).let { (cbor, hex) -> + val struct = cbor.encodeToCborElement(DataWithTags.serializer(), reference) + //hex is missing the tags, struct has them from the serializer + assertNotEquals(struct, cbor.decodeFromHexString(CborElement.serializer(), hex)) + assertEquals(reference, cbor.decodeFromCborElement(DataWithTags.serializer(), struct)) + } + + + assertEquals(reference, cborEncodingValueTags.decodeFromHexString(referenceHexString)) + (cborEncodingValueTags to referenceHexString).let { (cbor, hex) -> + val struct = cbor.encodeToCborElement(DataWithTags.serializer(), reference) + assertEquals(struct, cbor.decodeFromHexString(CborElement.serializer(), hex)) + assertEquals(reference, cbor.decodeFromCborElement(DataWithTags.serializer(), struct)) + } assertFailsWith(CborDecodingException::class) { Cbor { @@ -362,79 +413,84 @@ class CborTaggedTest { DataWithTags.serializer(), noValueTags ) + //Struct stuff has been tested in the above let blocks already } assertFailsWith(CborDecodingException::class) { - Cbor { - encodeKeyTags = true - encodeValueTags = true - encodeObjectTags = true - verifyKeyTags = true - verifyObjectTags = true - verifyValueTags = false - }.decodeFromHexString( + cborEncodingValueTags.decodeFromHexString( DataWithTags.serializer(), noKeyTags ) + //Struct stuff has been tested already } } @Test fun writeReadUntaggedEverything() { - assertEquals( - noTags, - Cbor { - encodeObjectTags = true - verifyKeyTags = true - verifyValueTags = true - verifyObjectTags = true - encodeValueTags = false - encodeKeyTags = false - }.encodeToHexString(DataWithTags.serializer(), reference) - ) - assertEquals( - noTagsDefLen, - Cbor { - encodeObjectTags = true - verifyKeyTags = true - verifyValueTags = true - verifyObjectTags = true - encodeValueTags = false - encodeKeyTags = false - useDefiniteLengthEncoding = true - }.encodeToHexString(DataWithTags.serializer(), reference) - ) - - assertEquals(reference, Cbor { - encodeKeyTags = true - encodeValueTags = true + val cborNoTags = Cbor { encodeObjectTags = true + verifyKeyTags = true + verifyValueTags = true verifyObjectTags = true - verifyKeyTags = false - verifyValueTags = false - }.decodeFromHexString(noTags)) + encodeValueTags = false + encodeKeyTags = false + } + assertEquals(noTags, cborNoTags.encodeToHexString(DataWithTags.serializer(), reference)) + (cborNoTags to noTags).let { (cbor, hex) -> + val struct = cbor.encodeToCborElement(DataWithTags.serializer(), reference) + assertEquals(struct, cbor.decodeFromHexString(CborElement.serializer(), hex)) + //struct is missing the tags + assertFailsWith(CborDecodingException::class) { + assertEquals(reference, cbor.decodeFromCborElement(DataWithTags.serializer(), struct)) + } + } - assertEquals(reference, Cbor { - encodeKeyTags = true - encodeValueTags = true + val cborNoTagsDefLen = Cbor { encodeObjectTags = true + verifyKeyTags = true + verifyValueTags = true verifyObjectTags = true - verifyKeyTags = false - verifyValueTags = false - }.decodeFromHexString(noTagsDefLen)) + encodeValueTags = false + encodeKeyTags = false + useDefiniteLengthEncoding = true + } + assertEquals(noTagsDefLen, cborNoTagsDefLen.encodeToHexString(DataWithTags.serializer(), reference)) + (cborNoTagsDefLen to noTagsDefLen).let { (cbor, hex) -> + val struct = cbor.encodeToCborElement(DataWithTags.serializer(), reference) + assertEquals(struct, cbor.decodeFromHexString(CborElement.serializer(), hex)) + //struct is missing the tags + assertFailsWith(CborDecodingException::class) { + assertEquals(reference, cbor.decodeFromCborElement(DataWithTags.serializer(), struct)) + } + } - assertEquals(reference, Cbor { + val cborEncodingAllVerifyingObjectTags = Cbor { encodeKeyTags = true encodeValueTags = true encodeObjectTags = true verifyObjectTags = true verifyKeyTags = false verifyValueTags = false - useDefiniteLengthEncoding = true - }.decodeFromHexString(noTags)) + } + assertEquals(reference, cborEncodingAllVerifyingObjectTags.decodeFromHexString(noTags)) + (cborEncodingAllVerifyingObjectTags to noTags).let { (cbor, hex) -> + val struct = cbor.encodeToCborElement(DataWithTags.serializer(), reference) + //hex has not tags but the current config encodes them into the struct + assertNotEquals(struct, cbor.decodeFromHexString(CborElement.serializer(), hex)) + assertEquals(reference, cbor.decodeFromCborElement(DataWithTags.serializer(), struct)) + } + + assertEquals(reference, cborEncodingAllVerifyingObjectTags.decodeFromHexString(noTagsDefLen)) + (cborEncodingAllVerifyingObjectTags to noTagsDefLen).let { (cbor, hex) -> + val struct = cbor.encodeToCborElement(DataWithTags.serializer(), reference) + //hex has not tags but the current config encodes them into the struct + assertNotEquals(struct, cbor.decodeFromHexString(CborElement.serializer(), hex)) + assertEquals(reference, cbor.decodeFromCborElement(DataWithTags.serializer(), struct)) + } + - assertEquals(reference, Cbor { + val cborEncodingAllDefLen = Cbor { encodeKeyTags = true encodeValueTags = true encodeObjectTags = true @@ -442,34 +498,45 @@ class CborTaggedTest { verifyKeyTags = false verifyValueTags = false useDefiniteLengthEncoding = true - }.decodeFromHexString(noTagsDefLen)) + } + assertEquals(reference, cborEncodingAllDefLen.decodeFromHexString(noTags)) + (cborEncodingAllDefLen to noTags).let { (cbor, hex) -> + val struct = cbor.encodeToCborElement(DataWithTags.serializer(), reference) + //hex has not tags but the current config encodes them into the struct + assertNotEquals(struct, cbor.decodeFromHexString(CborElement.serializer(), hex)) + assertEquals(reference, cbor.decodeFromCborElement(DataWithTags.serializer(), struct)) + } - assertEquals(reference, Cbor { - encodeKeyTags = true - encodeValueTags = true - encodeObjectTags = true - verifyObjectTags = true - verifyKeyTags = false - verifyValueTags = false - }.decodeFromHexString(noKeyTags)) + assertEquals(reference, cborEncodingAllDefLen.decodeFromHexString(noTagsDefLen)) + (cborEncodingAllDefLen to noTagsDefLen).let { (cbor, hex) -> + val struct = cbor.encodeToCborElement(DataWithTags.serializer(), reference) + //hex has not tags but the current config encodes them into the struct + assertNotEquals(struct, cbor.decodeFromHexString(CborElement.serializer(), hex)) + assertEquals(reference, cbor.decodeFromCborElement(DataWithTags.serializer(), struct)) + } - assertEquals(reference, Cbor { - encodeKeyTags = true - encodeValueTags = true - encodeObjectTags = true - verifyObjectTags = true - verifyKeyTags = false - verifyValueTags = false - }.decodeFromHexString(noValueTags)) + assertEquals(reference, cborEncodingAllVerifyingObjectTags.decodeFromHexString(noKeyTags)) + (cborEncodingAllVerifyingObjectTags to noKeyTags).let { (cbor, hex) -> + val struct = cbor.encodeToCborElement(DataWithTags.serializer(), reference) + //hex has not tags but the current config encodes them into the struct + assertNotEquals(struct, cbor.decodeFromHexString(CborElement.serializer(), hex)) + assertEquals(reference, cbor.decodeFromCborElement(DataWithTags.serializer(), struct)) + } - assertEquals(reference, Cbor { - encodeKeyTags = true - encodeValueTags = true - encodeObjectTags = true - verifyObjectTags = true - verifyKeyTags = false - verifyValueTags = false - }.decodeFromHexString(referenceHexString)) + assertEquals(reference, cborEncodingAllVerifyingObjectTags.decodeFromHexString(noValueTags)) + (cborEncodingAllVerifyingObjectTags to noValueTags).let { (cbor, hex) -> + val struct = cbor.encodeToCborElement(DataWithTags.serializer(), reference) + //hex has not tags but the current config encodes them into the struct + assertNotEquals(struct, cbor.decodeFromHexString(CborElement.serializer(), hex)) + assertEquals(reference, cbor.decodeFromCborElement(DataWithTags.serializer(), struct)) + } + + assertEquals(reference, cborEncodingAllVerifyingObjectTags.decodeFromHexString(referenceHexString)) + (cborEncodingAllVerifyingObjectTags to referenceHexString).let { (cbor, hex) -> + val struct = cbor.encodeToCborElement(DataWithTags.serializer(), reference) + assertEquals(struct, cbor.decodeFromHexString(CborElement.serializer(), hex)) + assertEquals(reference, cbor.decodeFromCborElement(DataWithTags.serializer(), struct)) + } assertFailsWith(CborDecodingException::class) { Cbor { @@ -483,6 +550,7 @@ class CborTaggedTest { DataWithTags.serializer(), noTags ) + //the struct stuff is already tested before } } @@ -520,6 +588,21 @@ class CborTaggedTest { ) }.message ?: "", "CBOR tags [55] do not match expected tags [56]" ) + + assertContains( + assertFailsWith( + CborDecodingException::class, + message = "CBOR tags [55] do not match declared tags [56]" + ) { + cbor.decodeFromCborElement( + DataWithTags.serializer(), cbor.decodeFromHexString( + CborElement.serializer(), + wrongTag55ForPropertyC + ) + ) + }.message ?: "", "CBOR tags [55] do not match expected tags [56]" + ) + } listOf( Cbor { @@ -540,6 +623,7 @@ class CborTaggedTest { verifyKeyTags = false }).forEach { cbor -> assertEquals(reference, cbor.decodeFromHexString(wrongTag55ForPropertyC)) + assertEquals(reference, cbor.decodeFromCborElement(cbor.decodeFromHexString(wrongTag55ForPropertyC))) } } @@ -571,27 +655,65 @@ class CborTaggedTest { assertEquals(referenceHexString, cbor.encodeToHexString(ClassAsTagged.serializer(), reference)) assertEquals(reference, cbor.decodeFromHexString(ClassAsTagged.serializer(), referenceHexString)) + (cbor to referenceHexString).let { (cbor, hexString) -> + val struct = cbor.encodeToCborElement(reference) + assertEquals(hexString, cbor.encodeToHexString(CborElement.serializer(), struct)) + assertEquals(cbor.decodeFromHexString(hexString), struct) + assertEquals(reference, cbor.decodeFromCborElement(ClassAsTagged.serializer(), struct)) + } + + + val cborNoVerifyObjTags = Cbor { verifyObjectTags = false } assertEquals( reference, - Cbor { verifyObjectTags = false }.decodeFromHexString(ClassAsTagged.serializer(), referenceHexString) + cborNoVerifyObjTags.decodeFromHexString(ClassAsTagged.serializer(), referenceHexString) ) + (cborNoVerifyObjTags to referenceHexString).let { (cbor, hexString) -> + val struct = cbor.encodeToCborElement(reference) + // NEQ: the ref string has object tags, but here we don't encode them + assertNotEquals(hexString, cbor.encodeToHexString(CborElement.serializer(), struct)) + // NEW, the hex string has the tags, so they are decoded, but the struct, created without object tags does not + assertNotEquals(cbor.decodeFromHexString(hexString), struct) + assertEquals(reference, cbor.decodeFromCborElement(ClassAsTagged.serializer(), struct)) + } + val cborNoEncodeObjTags = Cbor { encodeObjectTags = false } assertEquals( untaggedHexString, - Cbor { encodeObjectTags = false }.encodeToHexString(ClassAsTagged.serializer(), reference) + cborNoEncodeObjTags.encodeToHexString(ClassAsTagged.serializer(), reference) ) + (cborNoEncodeObjTags to untaggedHexString).let { (cbor, hexString) -> + val struct = cbor.encodeToCborElement(reference) + assertEquals(hexString, cbor.encodeToHexString(CborElement.serializer(), struct)) + assertEquals(cbor.decodeFromHexString(hexString), struct) + assertEquals(reference, cbor.decodeFromCborElement(ClassAsTagged.serializer(), struct)) + } assertEquals( reference, - Cbor { verifyObjectTags = false }.decodeFromHexString(ClassAsTagged.serializer(), untaggedHexString) + cborNoVerifyObjTags.decodeFromHexString(ClassAsTagged.serializer(), untaggedHexString) ) + (cborNoVerifyObjTags to untaggedHexString).let { (cbor, hexString) -> + val struct = cbor.encodeToCborElement(reference) + assertEquals(hexString, cbor.encodeToHexString(CborElement.serializer(), struct)) + assertEquals(cbor.decodeFromHexString(hexString), struct) + assertEquals(reference, cbor.decodeFromCborElement(ClassAsTagged.serializer(), struct)) + } assertContains( assertFailsWith(CborDecodingException::class) { cbor.decodeFromHexString(ClassAsTagged.serializer(), untaggedHexString) }.message ?: "", "do not match expected tags" ) + assertContains( + assertFailsWith(CborDecodingException::class) { + cbor.decodeFromCborElement( + ClassAsTagged.serializer(), + cbor.decodeFromHexString(CborElement.serializer(), untaggedHexString) + ) + }.message ?: "", "do not match expected tags" + ) /** * 81 # array(1) @@ -603,6 +725,10 @@ class CborTaggedTest { */ val listOfObjectTagged = listOf(reference) assertEquals("81d90539a163616c6713", Cbor.CoseCompliant.encodeToHexString(listOfObjectTagged)) + assertEquals( + "81d90539a163616c6713", + Cbor.CoseCompliant.encodeToHexString(Cbor.CoseCompliant.encodeToCborElement(listOfObjectTagged)) + ) } @@ -674,6 +800,12 @@ class CborTaggedTest { } assertEquals(referenceHexString, cbor.encodeToHexString(NestedTagged.serializer(), reference)) assertEquals(reference, cbor.decodeFromHexString(NestedTagged.serializer(), referenceHexString)) + (cbor to referenceHexString).let { (cbor, hex) -> + val struct = cbor.encodeToCborElement(reference) + assertEquals(struct, cbor.decodeFromHexString(hex)) + assertEquals(reference, cbor.decodeFromCborElement(struct)) + } + assertEquals( "More tags found than the 1 tags specified", @@ -681,6 +813,18 @@ class CborTaggedTest { cbor.decodeFromHexString(NestedTagged.serializer(), referenceHexStringWithBogusTag) }.message ) + assertEquals( + "CBOR tags [19, 20] do not match expected tags [19]", + assertFailsWith( + CborDecodingException::class, + message = "CBOR tags [19, 20] do not match expected tags [19]" + ) { + cbor.decodeFromCborElement( + NestedTagged.serializer(), + cbor.decodeFromHexString(CborElement.serializer(), referenceHexStringWithBogusTag) + ) + }.message + ) assertEquals( "CBOR tags null do not match expected tags [19]", @@ -688,56 +832,75 @@ class CborTaggedTest { cbor.decodeFromHexString(NestedTagged.serializer(), referenceHexStringWithMissingTag) }.message ) + assertEquals( + "CBOR tags null do not match expected tags [19]", + assertFailsWith(CborDecodingException::class, message = "CBOR tags null do not match expected tags [19]") { + cbor.decodeFromCborElement( + NestedTagged.serializer(), + cbor.decodeFromHexString(CborElement.serializer(), referenceHexStringWithMissingTag) + ) + }.message + ) + val cborNoVerifying = Cbor { + encodeKeyTags = true + encodeValueTags = true + encodeObjectTags = true + verifyKeyTags = true + verifyValueTags = true + verifyObjectTags = false + } assertEquals( reference, - Cbor { - encodeKeyTags = true - encodeValueTags = true - encodeObjectTags = true - verifyKeyTags = true - verifyValueTags = true - verifyObjectTags = false - }.decodeFromHexString(NestedTagged.serializer(), referenceHexString) + cborNoVerifying.decodeFromHexString(NestedTagged.serializer(), referenceHexString) ) + (cborNoVerifying to referenceHexString).let { (cbor, hex) -> + val struct = cbor.encodeToCborElement(reference) + assertEquals(struct, cbor.decodeFromHexString(hex)) + assertEquals(reference, cbor.decodeFromCborElement(struct)) + } assertEquals( reference, - Cbor { - encodeKeyTags = true - encodeValueTags = true - encodeObjectTags = true - verifyKeyTags = true - verifyValueTags = true - verifyObjectTags = false - }.decodeFromHexString(NestedTagged.serializer(), superfluousTagged) + cborNoVerifying.decodeFromHexString(NestedTagged.serializer(), superfluousTagged) ) + (cborNoVerifying to superfluousTagged).let { (cbor, hex) -> + val struct = cbor.encodeToCborElement(reference) + //there are more tags in the string + assertNotEquals(struct, cbor.decodeFromHexString(hex)) + assertEquals(reference, cbor.decodeFromCborElement(struct)) + } + val cborNoEncode = Cbor { + encodeKeyTags = true + encodeValueTags = true + verifyKeyTags = true + verifyValueTags = true + verifyObjectTags = false + encodeObjectTags = false + } assertEquals( untaggedHexString, - Cbor { - encodeKeyTags = true - encodeValueTags = true - verifyKeyTags = true - verifyValueTags = true - verifyObjectTags = true - encodeObjectTags = false - }.encodeToHexString(NestedTagged.serializer(), reference) + cborNoEncode.encodeToHexString(NestedTagged.serializer(), reference) ) + (cborNoEncode to untaggedHexString).let { (cbor, hex) -> + val struct = cbor.encodeToCborElement(reference) + assertEquals(struct, cbor.decodeFromHexString(hex)) + assertEquals(reference, cbor.decodeFromCborElement(struct)) + } assertEquals( reference, - Cbor { - encodeKeyTags = true - encodeValueTags = true - encodeObjectTags = true - verifyKeyTags = true - verifyValueTags = true - verifyObjectTags = false - }.decodeFromHexString(NestedTagged.serializer(), untaggedHexString) + cborNoVerifying.decodeFromHexString(NestedTagged.serializer(), untaggedHexString) ) + (cborNoVerifying to untaggedHexString).let { (cbor, hex) -> + val struct = cbor.encodeToCborElement(reference) + //NEQ: decoding from an untagged string means no tags coming in while encoding path above creates those tags + assertNotEquals(struct, cbor.decodeFromHexString(hex)) + assertEquals(reference, cbor.decodeFromCborElement(struct)) + } assertContains( assertFailsWith(CborDecodingException::class) { @@ -747,21 +910,21 @@ class CborTaggedTest { assertContains( assertFailsWith(CborDecodingException::class) { - Cbor { - encodeKeyTags = true - encodeValueTags = true - encodeObjectTags = true - verifyKeyTags = true - verifyValueTags = true - verifyObjectTags = false - }.decodeFromHexString( + cbor.decodeFromCborElement( + NestedTagged.serializer(), + cbor.decodeFromHexString(CborElement.serializer(), untaggedHexString) + ) + }.message ?: "", "do not match expected tags" + ) + + assertContains( + assertFailsWith(CborDecodingException::class) { + cborNoVerifying.decodeFromHexString( NestedTagged.serializer(), superfluousWrongTaggedTagged ) }.message ?: "", "do not start with specified tags" ) - - } @ObjectTags(1337uL) diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborWriterTest.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborWriterTest.kt index 330d4ff0f0..1f9718ea5a 100644 --- a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborWriterTest.kt +++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborWriterTest.kt @@ -27,10 +27,14 @@ class CbrWriterTest { HexConverter.parseHexBinary("cafe"), HexConverter.parseHexBinary("cafe") ) + val encoded = + "bf637374726d48656c6c6f2c20776f726c64216169182a686e756c6c61626c65f6646c6973749f61616162ff636d6170bf01f502f4ff65696e6e6572bf6161636c6f6cff6a696e6e6572734c6973749fbf6161636b656bffff6a62797465537472696e6742cafe696279746541727261799f383521ffff" assertEquals( - "bf637374726d48656c6c6f2c20776f726c64216169182a686e756c6c61626c65f6646c6973749f61616162ff636d6170bf01f502f4ff65696e6e6572bf6161636c6f6cff6a696e6e6572734c6973749fbf6161636b656bffff6a62797465537472696e6742cafe696279746541727261799f383521ffff", + encoded, Cbor.encodeToHexString(TypesUmbrella.serializer(), test) ) + val struct = Cbor.encodeToCborElement(test) + assertEquals(Cbor.decodeFromByteArray(encoded.hexToByteArray()), struct) } @Test @@ -46,10 +50,14 @@ class CbrWriterTest { HexConverter.parseHexBinary("cafe"), HexConverter.parseHexBinary("cafe") ) + val encoded = + "a9637374726d48656c6c6f2c20776f726c64216169182a686e756c6c61626c65f6646c6973748261616162636d6170a201f502f465696e6e6572a16161636c6f6c6a696e6e6572734c69737481a16161636b656b6a62797465537472696e6742cafe6962797465417272617982383521" assertEquals( - "a9637374726d48656c6c6f2c20776f726c64216169182a686e756c6c61626c65f6646c6973748261616162636d6170a201f502f465696e6e6572a16161636c6f6c6a696e6e6572734c69737481a16161636b656b6a62797465537472696e6742cafe6962797465417272617982383521", + encoded, Cbor { useDefiniteLengthEncoding = true }.encodeToHexString(TypesUmbrella.serializer(), test) ) + val struct = Cbor.encodeToCborElement(test) + assertEquals(Cbor.decodeFromByteArray(encoded.hexToByteArray()), struct) } @Test @@ -62,10 +70,14 @@ class CbrWriterTest { true, 'a' ) + val encoded = + "bf63696e741a00018894646c6f6e671b7fffffffffffffff65666c6f6174fa4228000066646f75626c65fb4271fb0c5a2b700067626f6f6c65616ef564636861721861ff" assertEquals( - "bf63696e741a00018894646c6f6e671b7fffffffffffffff65666c6f6174fa4228000066646f75626c65fb4271fb0c5a2b700067626f6f6c65616ef564636861721861ff", + encoded, Cbor.encodeToHexString(NumberTypesUmbrella.serializer(), test) ) + val struct = Cbor.encodeToCborElement(test) + assertEquals(Cbor.decodeFromByteArray(encoded.hexToByteArray()), struct) } @Test diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/SampleClasses.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/SampleClasses.kt index 8feb491191..b8f5bf994e 100644 --- a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/SampleClasses.kt +++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/SampleClasses.kt @@ -73,6 +73,7 @@ data class NullableByteString( @ByteString val byteString: ByteArray? ) { override fun equals(other: Any?): Boolean { + if (this === other) return true if (other == null || this::class != other::class) return false @@ -99,7 +100,7 @@ data class NullableByteStringDefaultNull( if (this === other) return true if (other == null || this::class != other::class) return false - other as NullableByteString + other as NullableByteStringDefaultNull if (byteString != null) { if (other.byteString == null) return false diff --git a/guide/example/example-formats-04.kt b/guide/example/example-formats-04.kt index 0ff0289feb..474927db86 100644 --- a/guide/example/example-formats-04.kt +++ b/guide/example/example-formats-04.kt @@ -1,6 +1,10 @@ // This file was automatically generated from formats.md by Knit tool. Do not edit. package example.exampleFormats04 +fun main() { + val element: CborElement = Cbor.decodeFromHexString("a165627974657343666f6f") + println(element) +} import kotlinx.serialization.* import kotlinx.serialization.protobuf.* diff --git a/guide/test/FormatsTest.kt b/guide/test/FormatsTest.kt index f305a59bf2..e8c84c77ff 100644 --- a/guide/test/FormatsTest.kt +++ b/guide/test/FormatsTest.kt @@ -28,6 +28,13 @@ class FormatsTest { ) } + @Test + fun testExampleFormats03() { + captureOutput("ExampleFormats03") { example.exampleFormats03.main() }.verifyOutputLines( + "CborMap(tags=[], content={CborString(tags=[], value=bytes)=CborByteString(tags=[], value=h'666f6f)})" + ) + } + @Test fun testExampleFormats04() { captureOutput("ExampleFormats04") { example.exampleFormats04.main() }.verifyOutputLines(