Add Cbor features for COSE compliance (#2412)

This PR contains all features required to serialize and parse COSE-compliant CBOR (thanks to @nodh). While some canonicalization steps (such as sorting keys) still need to be performed manually. It does get the job done quite well. Namely, we have successfully used the features introduced here to create and validate ISO/IEC 18013-5:2021-compliant mobile driving license data.

This PR introduces the following features to the CBOR format:

- Serial Labels
- Tagging of keys and values
- Definite length encoding (this is the largest change, as it effectively makes the cbor encoder two-pass)
- Option to globally prefer major type 2 for byte array encoding
- Various QoL changes, such as public CborEncoder/CborDecoder interfaces and separate CborConfiguration class.

This PR obsoletes #2371 and #2359 as it contains the features of both PRs and many more.

Fixes #1955
Fixes #1560

Co-authored-by: Christian Kollmann <christian.kollmann@a-sit.at>
Co-authored-by: Leonid Startsev <sandwwraith@users.noreply.github.com>
diff --git a/benchmark/build.gradle.kts b/benchmark/build.gradle.kts
index 36c9108..c63239c 100644
--- a/benchmark/build.gradle.kts
+++ b/benchmark/build.gradle.kts
@@ -61,6 +61,7 @@
     implementation(libs.okio)
     implementation(libs.kotlinx.io)
     implementation(project(":kotlinx-serialization-core"))
+    implementation(project(":kotlinx-serialization-cbor"))
     implementation(project(":kotlinx-serialization-json"))
     implementation(project(":kotlinx-serialization-json-okio"))
     implementation(project(":kotlinx-serialization-json-io"))
diff --git a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/cbor/CborBaseLine.kt b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/cbor/CborBaseLine.kt
new file mode 100644
index 0000000..8b71d92
--- /dev/null
+++ b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/cbor/CborBaseLine.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.benchmarks.cbor
+
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.cbor.*
+import org.openjdk.jmh.annotations.*
+import java.util.concurrent.*
+
+@Serializable
+data class KTestAllTypes(
+        val i32: Int,
+        val i64: Long,
+        val f: Float,
+        val d: Double,
+        val s: String,
+        val b: Boolean = false,
+    )
+
+@Serializable
+data class KTestOuterMessage(
+        val a: Int,
+        val b: Double,
+        val inner: KTestAllTypes,
+        val s: String,
+        val ss: List<String>
+    )
+
+@Warmup(iterations = 5, time = 1)
+@Measurement(iterations = 10, time = 1)
+@BenchmarkMode(Mode.Throughput)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Benchmark)
+@Fork(1)
+open class CborBaseline {
+        val baseMessage = KTestOuterMessage(
+                42,
+                256123123412.0,
+                s = "string",
+                ss = listOf("a", "b", "c"),
+                inner = KTestAllTypes(-123124512, 36253671257312, Float.MIN_VALUE, -23e15, "foobarbaz")
+                )
+
+        val cbor = Cbor {
+                encodeDefaults = true
+                encodeKeyTags = false
+                encodeValueTags = false
+                useDefiniteLengthEncoding = false
+                preferCborLabelsOverNames = false
+            }
+
+        val baseBytes = cbor.encodeToByteArray(KTestOuterMessage.serializer(), baseMessage)
+
+        @Benchmark
+        fun toBytes() = cbor.encodeToByteArray(KTestOuterMessage.serializer(), baseMessage)
+
+        @Benchmark
+        fun fromBytes() = cbor.decodeFromByteArray(KTestOuterMessage.serializer(), baseBytes)
+
+    }
diff --git a/buildSrc/src/main/kotlin/animalsniffer-conventions.gradle.kts b/buildSrc/src/main/kotlin/animalsniffer-conventions.gradle.kts
index 246dd3a..f280589 100644
--- a/buildSrc/src/main/kotlin/animalsniffer-conventions.gradle.kts
+++ b/buildSrc/src/main/kotlin/animalsniffer-conventions.gradle.kts
@@ -53,6 +53,7 @@
             "kotlinx-serialization-core" -> "kotlinx.serialization.internal.SuppressAnimalSniffer"
             "kotlinx-serialization-hocon" -> "kotlinx.serialization.hocon.internal.SuppressAnimalSniffer"
             "kotlinx-serialization-protobuf" -> "kotlinx.serialization.protobuf.internal.SuppressAnimalSniffer"
+            "kotlinx-serialization-cbor" -> "kotlinx.serialization.cbor.internal.SuppressAnimalSniffer"
             else -> "kotlinx.serialization.json.internal.SuppressAnimalSniffer"
         }
 
diff --git a/docs/formats.md b/docs/formats.md
index 8fb6eae..a6537e3 100644
--- a/docs/formats.md
+++ b/docs/formats.md
@@ -13,6 +13,10 @@
 * [CBOR (experimental)](#cbor-experimental)
   * [Ignoring unknown keys](#ignoring-unknown-keys)
   * [Byte arrays and CBOR data types](#byte-arrays-and-cbor-data-types)
+  * [Definite vs. Indefinite Length Encoding](#definite-vs-indefinite-length-encoding)
+  * [Tags and Labels](#tags-and-labels)
+  * [Arrays](#arrays)
+  * [Custom CBOR-specific Serializers](#custom-cbor-specific-serializers)
 * [ProtoBuf (experimental)](#protobuf-experimental)
   * [Field numbers](#field-numbers)
   * [Integer types](#integer-types)
@@ -164,6 +168,8 @@
 
 By default, Kotlin `ByteArray` instances are encoded as **major type 4**.
 When **major type 2** is desired, then the [`@ByteString`][ByteString] annotation can be used.
+Moreover, the `alwaysUseByteString` configuration switch allows for globally preferring **major type 2** without needing
+to annotate every `ByteArray` in a class hierarchy.
 
 <!--- INCLUDE
 import kotlinx.serialization.*
@@ -221,6 +227,90 @@
    FF            # primitive(*)
 ```
 
+### Definite vs. Indefinite Length Encoding
+CBOR supports two encodings for maps and arrays: definite and indefinite length encoding. kotlinx.serialization defaults
+to the latter, which means that a map's or array's number of elements is not encoded, but instead a terminating byte is
+appended after the last element.
+Definite length encoding, on the other hand, omits this terminating byte, but instead prepends number of elements
+to the contents of a map or array. The `useDefiniteLengthEncoding` configuration switch allows for toggling between the
+two modes of encoding.
+
+
+### Tags and Labels
+
+CBOR allows for optionally defining *tags* for properties and their values. These tags are encoded into the resulting
+byte string to transport additional information
+(see [RFC 8949 Tagging of Items](https://datatracker.ietf.org/doc/html/rfc8949#name-tagging-of-items) for more info).
+The  [`@KeyTags`](Tags.kt) and [`@ValueTags`](Tags.kt) annotations can be used to define such tags while
+writing and verifying such tags can be toggled using the  `encodeKeyTags`, `encodeValueTags`, `verifyKeyTags`, and
+`verifyValueTags` configuration switches respectively.
+In addition, it is possible to directly declare classes to always be tagged.
+This then applies to all instances of such a tagged class, regardless of whether they are used as values in a list
+or when they are used as a property in another class.
+Forcing objects to always be tagged in such a manner is accomplished by the [`@ObjectTags`](Tags.kt) annotation,
+which works just as `ValueTags`, but for class definitions.
+When serializing, `ObjectTags` will always be encoded directly before to the data of the tagged object, i.e. a
+value-tagged property of an object-tagged type will have the value tags preceding the object tags.
+Writing and verifying object tags can be toggled using the `encodeObjectTags` and `verifyObjectTags` configuration
+switches. Note that verifying only value tags can result in some data with superfluous tags to still deserialize
+successfully, since in this case - by definition - only a partial validation of tags happens.
+Well-known tags are specified in [`CborTag`](Tags.kt).
+
+In addition, CBOR supports keys of all types which work just as `SerialName`s.
+COSE restricts this again to strings and numbers and calls these restricted map keys *labels*. String labels can be
+assigned by using `@SerialName`, while number labels can be assigned using the [`@CborLabel`](CborLabel.kt) annotation.
+The `preferCborLabelsOverNames` configuration switch can be used to prefer number labels over SerialNames in case both
+are present for a property. This duality allows for compact representation of a type when serialized to CBOR, while
+keeping expressive diagnostic names when serializing to JSON.
+
+A predefined Cbor instance (in addition to the default [`Cbor.Default`](Cbor.kt) one) is available, adhering to COSE
+encoding requirements as [`Cbor.CoseCompliant`](Cbor.kt). This instance uses definite length encoding,
+encodes and verifies all tags and prefers labels to serial names.
+
+### Arrays
+
+Classes may be serialized as a CBOR Array (major type 4) instead of a CBOR Map (major type 5).
+
+Example usage:
+
+```
+@Serializable
+data class DataClass(
+    val alg: Int,
+    val kid: String?
+)
+
+Cbor.encodeToByteArray(DataClass(alg = -7, kid = null))
+```
+
+will normally produce a Cbor map: bytes `0xa263616c6726636b6964f6`, or in diagnostic notation:
+
+```
+A2           # map(2)
+   63        # text(3)
+      616C67 # "alg"
+   26        # negative(6)
+   63        # text(3)
+      6B6964 # "kid"
+   F6        # primitive(22)
+```
+
+When annotated with `@CborArray`, serialization of the same object will produce a Cbor array: bytes `0x8226F6`, or in diagnostic notation:
+
+```
+82    # array(2)
+   26 # negative(6)
+   F6 # primitive(22)
+```
+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.
+
 ## ProtoBuf (experimental)
 
 [Protocol Buffers](https://developers.google.com/protocol-buffers) is a language-neutral binary format that normally
diff --git a/docs/serialization-guide.md b/docs/serialization-guide.md
index 65ff69c..0724217 100644
--- a/docs/serialization-guide.md
+++ b/docs/serialization-guide.md
@@ -147,6 +147,10 @@
 * <a name='cbor-experimental'></a>[CBOR (experimental)](formats.md#cbor-experimental)
   * <a name='ignoring-unknown-keys'></a>[Ignoring unknown keys](formats.md#ignoring-unknown-keys)
   * <a name='byte-arrays-and-cbor-data-types'></a>[Byte arrays and CBOR data types](formats.md#byte-arrays-and-cbor-data-types)
+  * <a name='definite-vs-indefinite-length-encoding'></a>[Definite vs. Indefinite Length Encoding](formats.md#definite-vs-indefinite-length-encoding)
+  * <a name='tags-and-labels'></a>[Tags and Labels](formats.md#tags-and-labels)
+  * <a name='arrays'></a>[Arrays](formats.md#arrays)
+  * <a name='custom-cbor-specific-serializers'></a>[Custom CBOR-specific Serializers](formats.md#custom-cbor-specific-serializers)
 * <a name='protobuf-experimental'></a>[ProtoBuf (experimental)](formats.md#protobuf-experimental)
   * <a name='field-numbers'></a>[Field numbers](formats.md#field-numbers)
   * <a name='integer-types'></a>[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 cc75fab..8b58032 100644
--- a/formats/cbor/api/kotlinx-serialization-cbor.api
+++ b/formats/cbor/api/kotlinx-serialization-cbor.api
@@ -7,22 +7,84 @@
 
 public abstract class kotlinx/serialization/cbor/Cbor : kotlinx/serialization/BinaryFormat {
 	public static final field Default Lkotlinx/serialization/cbor/Cbor$Default;
-	public synthetic fun <init> (ZZLkotlinx/serialization/modules/SerializersModule;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+	public synthetic fun <init> (Lkotlinx/serialization/cbor/CborConfiguration;Lkotlinx/serialization/modules/SerializersModule;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
 	public fun decodeFromByteArray (Lkotlinx/serialization/DeserializationStrategy;[B)Ljava/lang/Object;
 	public fun encodeToByteArray (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)[B
+	public final fun getConfiguration ()Lkotlinx/serialization/cbor/CborConfiguration;
 	public fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule;
 }
 
 public final class kotlinx/serialization/cbor/Cbor$Default : kotlinx/serialization/cbor/Cbor {
+	public final fun getCoseCompliant ()Lkotlinx/serialization/cbor/Cbor;
+}
+
+public abstract interface annotation class kotlinx/serialization/cbor/CborArray : java/lang/annotation/Annotation {
+}
+
+public synthetic class kotlinx/serialization/cbor/CborArray$Impl : kotlinx/serialization/cbor/CborArray {
+	public fun <init> ()V
 }
 
 public final class kotlinx/serialization/cbor/CborBuilder {
+	public final fun getAlwaysUseByteString ()Z
 	public final fun getEncodeDefaults ()Z
+	public final fun getEncodeKeyTags ()Z
+	public final fun getEncodeObjectTags ()Z
+	public final fun getEncodeValueTags ()Z
 	public final fun getIgnoreUnknownKeys ()Z
+	public final fun getPreferCborLabelsOverNames ()Z
 	public final fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule;
+	public final fun getUseDefiniteLengthEncoding ()Z
+	public final fun getVerifyKeyTags ()Z
+	public final fun getVerifyObjectTags ()Z
+	public final fun getVerifyValueTags ()Z
+	public final fun setAlwaysUseByteString (Z)V
 	public final fun setEncodeDefaults (Z)V
+	public final fun setEncodeKeyTags (Z)V
+	public final fun setEncodeObjectTags (Z)V
+	public final fun setEncodeValueTags (Z)V
 	public final fun setIgnoreUnknownKeys (Z)V
+	public final fun setPreferCborLabelsOverNames (Z)V
 	public final fun setSerializersModule (Lkotlinx/serialization/modules/SerializersModule;)V
+	public final fun setUseDefiniteLengthEncoding (Z)V
+	public final fun setVerifyKeyTags (Z)V
+	public final fun setVerifyObjectTags (Z)V
+	public final fun setVerifyValueTags (Z)V
+}
+
+public final class kotlinx/serialization/cbor/CborConfiguration {
+	public final fun getAlwaysUseByteString ()Z
+	public final fun getEncodeDefaults ()Z
+	public final fun getEncodeKeyTags ()Z
+	public final fun getEncodeObjectTags ()Z
+	public final fun getEncodeValueTags ()Z
+	public final fun getIgnoreUnknownKeys ()Z
+	public final fun getPreferCborLabelsOverNames ()Z
+	public final fun getUseDefiniteLengthEncoding ()Z
+	public final fun getVerifyKeyTags ()Z
+	public final fun getVerifyObjectTags ()Z
+	public final fun getVerifyValueTags ()Z
+	public fun toString ()Ljava/lang/String;
+}
+
+public abstract interface class kotlinx/serialization/cbor/CborDecoder : kotlinx/serialization/encoding/Decoder {
+	public abstract fun getCbor ()Lkotlinx/serialization/cbor/Cbor;
+}
+
+public final class kotlinx/serialization/cbor/CborDecoder$DefaultImpls {
+	public static fun decodeNullableSerializableValue (Lkotlinx/serialization/cbor/CborDecoder;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object;
+	public static fun decodeSerializableValue (Lkotlinx/serialization/cbor/CborDecoder;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object;
+}
+
+public abstract interface class kotlinx/serialization/cbor/CborEncoder : kotlinx/serialization/encoding/Encoder {
+	public abstract fun getCbor ()Lkotlinx/serialization/cbor/Cbor;
+}
+
+public final class kotlinx/serialization/cbor/CborEncoder$DefaultImpls {
+	public static fun beginCollection (Lkotlinx/serialization/cbor/CborEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;I)Lkotlinx/serialization/encoding/CompositeEncoder;
+	public static fun encodeNotNullMark (Lkotlinx/serialization/cbor/CborEncoder;)V
+	public static fun encodeNullableSerializableValue (Lkotlinx/serialization/cbor/CborEncoder;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V
+	public static fun encodeSerializableValue (Lkotlinx/serialization/cbor/CborEncoder;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V
 }
 
 public final class kotlinx/serialization/cbor/CborKt {
@@ -30,3 +92,59 @@
 	public static synthetic fun Cbor$default (Lkotlinx/serialization/cbor/Cbor;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlinx/serialization/cbor/Cbor;
 }
 
+public abstract interface annotation class kotlinx/serialization/cbor/CborLabel : java/lang/annotation/Annotation {
+	public abstract fun label ()J
+}
+
+public synthetic class kotlinx/serialization/cbor/CborLabel$Impl : kotlinx/serialization/cbor/CborLabel {
+	public fun <init> (J)V
+	public final synthetic fun label ()J
+}
+
+public final class kotlinx/serialization/cbor/CborTag {
+	public static final field BASE16 J
+	public static final field BASE64 J
+	public static final field BASE64_URL J
+	public static final field BIGFLOAT J
+	public static final field BIGNUM_NEGAIVE J
+	public static final field BIGNUM_POSITIVE J
+	public static final field CBOR_ENCODED_DATA J
+	public static final field CBOR_SELF_DESCRIBE J
+	public static final field DATE_TIME_EPOCH J
+	public static final field DATE_TIME_STANDARD J
+	public static final field DECIMAL_FRACTION J
+	public static final field INSTANCE Lkotlinx/serialization/cbor/CborTag;
+	public static final field MIME_MESSAGE J
+	public static final field REGEX J
+	public static final field STRING_BASE64 J
+	public static final field STRING_BASE64_URL J
+	public static final field URI J
+}
+
+public abstract interface annotation class kotlinx/serialization/cbor/KeyTags : java/lang/annotation/Annotation {
+	public abstract fun tags ()[J
+}
+
+public synthetic class kotlinx/serialization/cbor/KeyTags$Impl : kotlinx/serialization/cbor/KeyTags {
+	public synthetic fun <init> ([JLkotlin/jvm/internal/DefaultConstructorMarker;)V
+	public final synthetic fun tags ()[J
+}
+
+public abstract interface annotation class kotlinx/serialization/cbor/ObjectTags : java/lang/annotation/Annotation {
+	public abstract fun tags ()[J
+}
+
+public synthetic class kotlinx/serialization/cbor/ObjectTags$Impl : kotlinx/serialization/cbor/ObjectTags {
+	public synthetic fun <init> ([JLkotlin/jvm/internal/DefaultConstructorMarker;)V
+	public final synthetic fun tags ()[J
+}
+
+public abstract interface annotation class kotlinx/serialization/cbor/ValueTags : java/lang/annotation/Annotation {
+	public abstract fun tags ()[J
+}
+
+public synthetic class kotlinx/serialization/cbor/ValueTags$Impl : kotlinx/serialization/cbor/ValueTags {
+	public synthetic fun <init> ([JLkotlin/jvm/internal/DefaultConstructorMarker;)V
+	public final synthetic fun tags ()[J
+}
+
diff --git a/formats/cbor/api/kotlinx-serialization-cbor.klib.api b/formats/cbor/api/kotlinx-serialization-cbor.klib.api
index 0064161..d960702 100644
--- a/formats/cbor/api/kotlinx-serialization-cbor.klib.api
+++ b/formats/cbor/api/kotlinx-serialization-cbor.klib.api
@@ -6,24 +6,146 @@
 // - Show declarations: true
 
 // Library unique name: <org.jetbrains.kotlinx:kotlinx-serialization-cbor>
+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 <get-cbor>(): kotlinx.serialization.cbor/Cbor // kotlinx.serialization.cbor/CborDecoder.cbor.<get-cbor>|<get-cbor>(){}[0]
+}
+abstract interface kotlinx.serialization.cbor/CborEncoder : kotlinx.serialization.encoding/Encoder { // kotlinx.serialization.cbor/CborEncoder|null[0]
+    abstract val cbor // kotlinx.serialization.cbor/CborEncoder.cbor|{}cbor[0]
+        abstract fun <get-cbor>(): kotlinx.serialization.cbor/Cbor // kotlinx.serialization.cbor/CborEncoder.cbor.<get-cbor>|<get-cbor>(){}[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 <get-alwaysUseByteString>(): kotlin/Boolean // kotlinx.serialization.cbor/CborBuilder.alwaysUseByteString.<get-alwaysUseByteString>|<get-alwaysUseByteString>(){}[0]
+        final fun <set-alwaysUseByteString>(kotlin/Boolean) // kotlinx.serialization.cbor/CborBuilder.alwaysUseByteString.<set-alwaysUseByteString>|<set-alwaysUseByteString>(kotlin.Boolean){}[0]
     final var encodeDefaults // kotlinx.serialization.cbor/CborBuilder.encodeDefaults|{}encodeDefaults[0]
         final fun <get-encodeDefaults>(): kotlin/Boolean // kotlinx.serialization.cbor/CborBuilder.encodeDefaults.<get-encodeDefaults>|<get-encodeDefaults>(){}[0]
         final fun <set-encodeDefaults>(kotlin/Boolean) // kotlinx.serialization.cbor/CborBuilder.encodeDefaults.<set-encodeDefaults>|<set-encodeDefaults>(kotlin.Boolean){}[0]
+    final var encodeKeyTags // kotlinx.serialization.cbor/CborBuilder.encodeKeyTags|{}encodeKeyTags[0]
+        final fun <get-encodeKeyTags>(): kotlin/Boolean // kotlinx.serialization.cbor/CborBuilder.encodeKeyTags.<get-encodeKeyTags>|<get-encodeKeyTags>(){}[0]
+        final fun <set-encodeKeyTags>(kotlin/Boolean) // kotlinx.serialization.cbor/CborBuilder.encodeKeyTags.<set-encodeKeyTags>|<set-encodeKeyTags>(kotlin.Boolean){}[0]
+    final var encodeObjectTags // kotlinx.serialization.cbor/CborBuilder.encodeObjectTags|{}encodeObjectTags[0]
+        final fun <get-encodeObjectTags>(): kotlin/Boolean // kotlinx.serialization.cbor/CborBuilder.encodeObjectTags.<get-encodeObjectTags>|<get-encodeObjectTags>(){}[0]
+        final fun <set-encodeObjectTags>(kotlin/Boolean) // kotlinx.serialization.cbor/CborBuilder.encodeObjectTags.<set-encodeObjectTags>|<set-encodeObjectTags>(kotlin.Boolean){}[0]
+    final var encodeValueTags // kotlinx.serialization.cbor/CborBuilder.encodeValueTags|{}encodeValueTags[0]
+        final fun <get-encodeValueTags>(): kotlin/Boolean // kotlinx.serialization.cbor/CborBuilder.encodeValueTags.<get-encodeValueTags>|<get-encodeValueTags>(){}[0]
+        final fun <set-encodeValueTags>(kotlin/Boolean) // kotlinx.serialization.cbor/CborBuilder.encodeValueTags.<set-encodeValueTags>|<set-encodeValueTags>(kotlin.Boolean){}[0]
     final var ignoreUnknownKeys // kotlinx.serialization.cbor/CborBuilder.ignoreUnknownKeys|{}ignoreUnknownKeys[0]
         final fun <get-ignoreUnknownKeys>(): kotlin/Boolean // kotlinx.serialization.cbor/CborBuilder.ignoreUnknownKeys.<get-ignoreUnknownKeys>|<get-ignoreUnknownKeys>(){}[0]
         final fun <set-ignoreUnknownKeys>(kotlin/Boolean) // kotlinx.serialization.cbor/CborBuilder.ignoreUnknownKeys.<set-ignoreUnknownKeys>|<set-ignoreUnknownKeys>(kotlin.Boolean){}[0]
+    final var preferCborLabelsOverNames // kotlinx.serialization.cbor/CborBuilder.preferCborLabelsOverNames|{}preferCborLabelsOverNames[0]
+        final fun <get-preferCborLabelsOverNames>(): kotlin/Boolean // kotlinx.serialization.cbor/CborBuilder.preferCborLabelsOverNames.<get-preferCborLabelsOverNames>|<get-preferCborLabelsOverNames>(){}[0]
+        final fun <set-preferCborLabelsOverNames>(kotlin/Boolean) // kotlinx.serialization.cbor/CborBuilder.preferCborLabelsOverNames.<set-preferCborLabelsOverNames>|<set-preferCborLabelsOverNames>(kotlin.Boolean){}[0]
     final var serializersModule // kotlinx.serialization.cbor/CborBuilder.serializersModule|{}serializersModule[0]
         final fun <get-serializersModule>(): kotlinx.serialization.modules/SerializersModule // kotlinx.serialization.cbor/CborBuilder.serializersModule.<get-serializersModule>|<get-serializersModule>(){}[0]
         final fun <set-serializersModule>(kotlinx.serialization.modules/SerializersModule) // kotlinx.serialization.cbor/CborBuilder.serializersModule.<set-serializersModule>|<set-serializersModule>(kotlinx.serialization.modules.SerializersModule){}[0]
+    final var useDefiniteLengthEncoding // kotlinx.serialization.cbor/CborBuilder.useDefiniteLengthEncoding|{}useDefiniteLengthEncoding[0]
+        final fun <get-useDefiniteLengthEncoding>(): kotlin/Boolean // kotlinx.serialization.cbor/CborBuilder.useDefiniteLengthEncoding.<get-useDefiniteLengthEncoding>|<get-useDefiniteLengthEncoding>(){}[0]
+        final fun <set-useDefiniteLengthEncoding>(kotlin/Boolean) // kotlinx.serialization.cbor/CborBuilder.useDefiniteLengthEncoding.<set-useDefiniteLengthEncoding>|<set-useDefiniteLengthEncoding>(kotlin.Boolean){}[0]
+    final var verifyKeyTags // kotlinx.serialization.cbor/CborBuilder.verifyKeyTags|{}verifyKeyTags[0]
+        final fun <get-verifyKeyTags>(): kotlin/Boolean // kotlinx.serialization.cbor/CborBuilder.verifyKeyTags.<get-verifyKeyTags>|<get-verifyKeyTags>(){}[0]
+        final fun <set-verifyKeyTags>(kotlin/Boolean) // kotlinx.serialization.cbor/CborBuilder.verifyKeyTags.<set-verifyKeyTags>|<set-verifyKeyTags>(kotlin.Boolean){}[0]
+    final var verifyObjectTags // kotlinx.serialization.cbor/CborBuilder.verifyObjectTags|{}verifyObjectTags[0]
+        final fun <get-verifyObjectTags>(): kotlin/Boolean // kotlinx.serialization.cbor/CborBuilder.verifyObjectTags.<get-verifyObjectTags>|<get-verifyObjectTags>(){}[0]
+        final fun <set-verifyObjectTags>(kotlin/Boolean) // kotlinx.serialization.cbor/CborBuilder.verifyObjectTags.<set-verifyObjectTags>|<set-verifyObjectTags>(kotlin.Boolean){}[0]
+    final var verifyValueTags // kotlinx.serialization.cbor/CborBuilder.verifyValueTags|{}verifyValueTags[0]
+        final fun <get-verifyValueTags>(): kotlin/Boolean // kotlinx.serialization.cbor/CborBuilder.verifyValueTags.<get-verifyValueTags>|<get-verifyValueTags>(){}[0]
+        final fun <set-verifyValueTags>(kotlin/Boolean) // kotlinx.serialization.cbor/CborBuilder.verifyValueTags.<set-verifyValueTags>|<set-verifyValueTags>(kotlin.Boolean){}[0]
+}
+final class kotlinx.serialization.cbor/CborConfiguration { // kotlinx.serialization.cbor/CborConfiguration|null[0]
+    final fun toString(): kotlin/String // kotlinx.serialization.cbor/CborConfiguration.toString|toString(){}[0]
+    final val alwaysUseByteString // kotlinx.serialization.cbor/CborConfiguration.alwaysUseByteString|{}alwaysUseByteString[0]
+        final fun <get-alwaysUseByteString>(): kotlin/Boolean // kotlinx.serialization.cbor/CborConfiguration.alwaysUseByteString.<get-alwaysUseByteString>|<get-alwaysUseByteString>(){}[0]
+    final val encodeDefaults // kotlinx.serialization.cbor/CborConfiguration.encodeDefaults|{}encodeDefaults[0]
+        final fun <get-encodeDefaults>(): kotlin/Boolean // kotlinx.serialization.cbor/CborConfiguration.encodeDefaults.<get-encodeDefaults>|<get-encodeDefaults>(){}[0]
+    final val encodeKeyTags // kotlinx.serialization.cbor/CborConfiguration.encodeKeyTags|{}encodeKeyTags[0]
+        final fun <get-encodeKeyTags>(): kotlin/Boolean // kotlinx.serialization.cbor/CborConfiguration.encodeKeyTags.<get-encodeKeyTags>|<get-encodeKeyTags>(){}[0]
+    final val encodeObjectTags // kotlinx.serialization.cbor/CborConfiguration.encodeObjectTags|{}encodeObjectTags[0]
+        final fun <get-encodeObjectTags>(): kotlin/Boolean // kotlinx.serialization.cbor/CborConfiguration.encodeObjectTags.<get-encodeObjectTags>|<get-encodeObjectTags>(){}[0]
+    final val encodeValueTags // kotlinx.serialization.cbor/CborConfiguration.encodeValueTags|{}encodeValueTags[0]
+        final fun <get-encodeValueTags>(): kotlin/Boolean // kotlinx.serialization.cbor/CborConfiguration.encodeValueTags.<get-encodeValueTags>|<get-encodeValueTags>(){}[0]
+    final val ignoreUnknownKeys // kotlinx.serialization.cbor/CborConfiguration.ignoreUnknownKeys|{}ignoreUnknownKeys[0]
+        final fun <get-ignoreUnknownKeys>(): kotlin/Boolean // kotlinx.serialization.cbor/CborConfiguration.ignoreUnknownKeys.<get-ignoreUnknownKeys>|<get-ignoreUnknownKeys>(){}[0]
+    final val preferCborLabelsOverNames // kotlinx.serialization.cbor/CborConfiguration.preferCborLabelsOverNames|{}preferCborLabelsOverNames[0]
+        final fun <get-preferCborLabelsOverNames>(): kotlin/Boolean // kotlinx.serialization.cbor/CborConfiguration.preferCborLabelsOverNames.<get-preferCborLabelsOverNames>|<get-preferCborLabelsOverNames>(){}[0]
+    final val useDefiniteLengthEncoding // kotlinx.serialization.cbor/CborConfiguration.useDefiniteLengthEncoding|{}useDefiniteLengthEncoding[0]
+        final fun <get-useDefiniteLengthEncoding>(): kotlin/Boolean // kotlinx.serialization.cbor/CborConfiguration.useDefiniteLengthEncoding.<get-useDefiniteLengthEncoding>|<get-useDefiniteLengthEncoding>(){}[0]
+    final val verifyKeyTags // kotlinx.serialization.cbor/CborConfiguration.verifyKeyTags|{}verifyKeyTags[0]
+        final fun <get-verifyKeyTags>(): kotlin/Boolean // kotlinx.serialization.cbor/CborConfiguration.verifyKeyTags.<get-verifyKeyTags>|<get-verifyKeyTags>(){}[0]
+    final val verifyObjectTags // kotlinx.serialization.cbor/CborConfiguration.verifyObjectTags|{}verifyObjectTags[0]
+        final fun <get-verifyObjectTags>(): kotlin/Boolean // kotlinx.serialization.cbor/CborConfiguration.verifyObjectTags.<get-verifyObjectTags>|<get-verifyObjectTags>(){}[0]
+    final val verifyValueTags // kotlinx.serialization.cbor/CborConfiguration.verifyValueTags|{}verifyValueTags[0]
+        final fun <get-verifyValueTags>(): kotlin/Boolean // kotlinx.serialization.cbor/CborConfiguration.verifyValueTags.<get-verifyValueTags>|<get-verifyValueTags>(){}[0]
 }
 final fun kotlinx.serialization.cbor/Cbor(kotlinx.serialization.cbor/Cbor = ..., kotlin/Function1<kotlinx.serialization.cbor/CborBuilder, kotlin/Unit>): kotlinx.serialization.cbor/Cbor // kotlinx.serialization.cbor/Cbor|Cbor(kotlinx.serialization.cbor.Cbor;kotlin.Function1<kotlinx.serialization.cbor.CborBuilder,kotlin.Unit>){}[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 <get-BASE16>(): kotlin/ULong // kotlinx.serialization.cbor/CborTag.BASE16.<get-BASE16>|<get-BASE16>(){}[0]
+    final const val BASE64 // kotlinx.serialization.cbor/CborTag.BASE64|{}BASE64[0]
+        final fun <get-BASE64>(): kotlin/ULong // kotlinx.serialization.cbor/CborTag.BASE64.<get-BASE64>|<get-BASE64>(){}[0]
+    final const val BASE64_URL // kotlinx.serialization.cbor/CborTag.BASE64_URL|{}BASE64_URL[0]
+        final fun <get-BASE64_URL>(): kotlin/ULong // kotlinx.serialization.cbor/CborTag.BASE64_URL.<get-BASE64_URL>|<get-BASE64_URL>(){}[0]
+    final const val BIGFLOAT // kotlinx.serialization.cbor/CborTag.BIGFLOAT|{}BIGFLOAT[0]
+        final fun <get-BIGFLOAT>(): kotlin/ULong // kotlinx.serialization.cbor/CborTag.BIGFLOAT.<get-BIGFLOAT>|<get-BIGFLOAT>(){}[0]
+    final const val BIGNUM_NEGAIVE // kotlinx.serialization.cbor/CborTag.BIGNUM_NEGAIVE|{}BIGNUM_NEGAIVE[0]
+        final fun <get-BIGNUM_NEGAIVE>(): kotlin/ULong // kotlinx.serialization.cbor/CborTag.BIGNUM_NEGAIVE.<get-BIGNUM_NEGAIVE>|<get-BIGNUM_NEGAIVE>(){}[0]
+    final const val BIGNUM_POSITIVE // kotlinx.serialization.cbor/CborTag.BIGNUM_POSITIVE|{}BIGNUM_POSITIVE[0]
+        final fun <get-BIGNUM_POSITIVE>(): kotlin/ULong // kotlinx.serialization.cbor/CborTag.BIGNUM_POSITIVE.<get-BIGNUM_POSITIVE>|<get-BIGNUM_POSITIVE>(){}[0]
+    final const val CBOR_ENCODED_DATA // kotlinx.serialization.cbor/CborTag.CBOR_ENCODED_DATA|{}CBOR_ENCODED_DATA[0]
+        final fun <get-CBOR_ENCODED_DATA>(): kotlin/ULong // kotlinx.serialization.cbor/CborTag.CBOR_ENCODED_DATA.<get-CBOR_ENCODED_DATA>|<get-CBOR_ENCODED_DATA>(){}[0]
+    final const val CBOR_SELF_DESCRIBE // kotlinx.serialization.cbor/CborTag.CBOR_SELF_DESCRIBE|{}CBOR_SELF_DESCRIBE[0]
+        final fun <get-CBOR_SELF_DESCRIBE>(): kotlin/ULong // kotlinx.serialization.cbor/CborTag.CBOR_SELF_DESCRIBE.<get-CBOR_SELF_DESCRIBE>|<get-CBOR_SELF_DESCRIBE>(){}[0]
+    final const val DATE_TIME_EPOCH // kotlinx.serialization.cbor/CborTag.DATE_TIME_EPOCH|{}DATE_TIME_EPOCH[0]
+        final fun <get-DATE_TIME_EPOCH>(): kotlin/ULong // kotlinx.serialization.cbor/CborTag.DATE_TIME_EPOCH.<get-DATE_TIME_EPOCH>|<get-DATE_TIME_EPOCH>(){}[0]
+    final const val DATE_TIME_STANDARD // kotlinx.serialization.cbor/CborTag.DATE_TIME_STANDARD|{}DATE_TIME_STANDARD[0]
+        final fun <get-DATE_TIME_STANDARD>(): kotlin/ULong // kotlinx.serialization.cbor/CborTag.DATE_TIME_STANDARD.<get-DATE_TIME_STANDARD>|<get-DATE_TIME_STANDARD>(){}[0]
+    final const val DECIMAL_FRACTION // kotlinx.serialization.cbor/CborTag.DECIMAL_FRACTION|{}DECIMAL_FRACTION[0]
+        final fun <get-DECIMAL_FRACTION>(): kotlin/ULong // kotlinx.serialization.cbor/CborTag.DECIMAL_FRACTION.<get-DECIMAL_FRACTION>|<get-DECIMAL_FRACTION>(){}[0]
+    final const val MIME_MESSAGE // kotlinx.serialization.cbor/CborTag.MIME_MESSAGE|{}MIME_MESSAGE[0]
+        final fun <get-MIME_MESSAGE>(): kotlin/ULong // kotlinx.serialization.cbor/CborTag.MIME_MESSAGE.<get-MIME_MESSAGE>|<get-MIME_MESSAGE>(){}[0]
+    final const val REGEX // kotlinx.serialization.cbor/CborTag.REGEX|{}REGEX[0]
+        final fun <get-REGEX>(): kotlin/ULong // kotlinx.serialization.cbor/CborTag.REGEX.<get-REGEX>|<get-REGEX>(){}[0]
+    final const val STRING_BASE64 // kotlinx.serialization.cbor/CborTag.STRING_BASE64|{}STRING_BASE64[0]
+        final fun <get-STRING_BASE64>(): kotlin/ULong // kotlinx.serialization.cbor/CborTag.STRING_BASE64.<get-STRING_BASE64>|<get-STRING_BASE64>(){}[0]
+    final const val STRING_BASE64_URL // kotlinx.serialization.cbor/CborTag.STRING_BASE64_URL|{}STRING_BASE64_URL[0]
+        final fun <get-STRING_BASE64_URL>(): kotlin/ULong // kotlinx.serialization.cbor/CborTag.STRING_BASE64_URL.<get-STRING_BASE64_URL>|<get-STRING_BASE64_URL>(){}[0]
+    final const val URI // kotlinx.serialization.cbor/CborTag.URI|{}URI[0]
+        final fun <get-URI>(): kotlin/ULong // kotlinx.serialization.cbor/CborTag.URI.<get-URI>|<get-URI>(){}[0]
+}
 open annotation class kotlinx.serialization.cbor/ByteString : kotlin/Annotation { // kotlinx.serialization.cbor/ByteString|null[0]
     constructor <init>() // kotlinx.serialization.cbor/ByteString.<init>|<init>(){}[0]
 }
+open annotation class kotlinx.serialization.cbor/CborArray : kotlin/Annotation { // kotlinx.serialization.cbor/CborArray|null[0]
+    constructor <init>() // kotlinx.serialization.cbor/CborArray.<init>|<init>(){}[0]
+}
+open annotation class kotlinx.serialization.cbor/CborLabel : kotlin/Annotation { // kotlinx.serialization.cbor/CborLabel|null[0]
+    constructor <init>(kotlin/Long) // kotlinx.serialization.cbor/CborLabel.<init>|<init>(kotlin.Long){}[0]
+    final val label // kotlinx.serialization.cbor/CborLabel.label|{}label[0]
+        final fun <get-label>(): kotlin/Long // kotlinx.serialization.cbor/CborLabel.label.<get-label>|<get-label>(){}[0]
+}
+open annotation class kotlinx.serialization.cbor/KeyTags : kotlin/Annotation { // kotlinx.serialization.cbor/KeyTags|null[0]
+    constructor <init>(kotlin/ULongArray...) // kotlinx.serialization.cbor/KeyTags.<init>|<init>(kotlin.ULongArray...){}[0]
+    final val tags // kotlinx.serialization.cbor/KeyTags.tags|{}tags[0]
+        final fun <get-tags>(): kotlin/ULongArray // kotlinx.serialization.cbor/KeyTags.tags.<get-tags>|<get-tags>(){}[0]
+}
+open annotation class kotlinx.serialization.cbor/ObjectTags : kotlin/Annotation { // kotlinx.serialization.cbor/ObjectTags|null[0]
+    constructor <init>(kotlin/ULongArray...) // kotlinx.serialization.cbor/ObjectTags.<init>|<init>(kotlin.ULongArray...){}[0]
+    final val tags // kotlinx.serialization.cbor/ObjectTags.tags|{}tags[0]
+        final fun <get-tags>(): kotlin/ULongArray // kotlinx.serialization.cbor/ObjectTags.tags.<get-tags>|<get-tags>(){}[0]
+}
+open annotation class kotlinx.serialization.cbor/ValueTags : kotlin/Annotation { // kotlinx.serialization.cbor/ValueTags|null[0]
+    constructor <init>(kotlin/ULongArray...) // kotlinx.serialization.cbor/ValueTags.<init>|<init>(kotlin.ULongArray...){}[0]
+    final val tags // kotlinx.serialization.cbor/ValueTags.tags|{}tags[0]
+        final fun <get-tags>(): kotlin/ULongArray // kotlinx.serialization.cbor/ValueTags.tags.<get-tags>|<get-tags>(){}[0]
+}
 sealed class kotlinx.serialization.cbor/Cbor : kotlinx.serialization/BinaryFormat { // kotlinx.serialization.cbor/Cbor|null[0]
-    constructor <init>(kotlin/Boolean, kotlin/Boolean, kotlinx.serialization.modules/SerializersModule) // kotlinx.serialization.cbor/Cbor.<init>|<init>(kotlin.Boolean;kotlin.Boolean;kotlinx.serialization.modules.SerializersModule){}[0]
-    final object Default : kotlinx.serialization.cbor/Cbor // kotlinx.serialization.cbor/Cbor.Default|null[0]
+    constructor <init>(kotlinx.serialization.cbor/CborConfiguration, kotlinx.serialization.modules/SerializersModule) // kotlinx.serialization.cbor/Cbor.<init>|<init>(kotlinx.serialization.cbor.CborConfiguration;kotlinx.serialization.modules.SerializersModule){}[0]
+    final object Default : kotlinx.serialization.cbor/Cbor { // kotlinx.serialization.cbor/Cbor.Default|null[0]
+        final val CoseCompliant // kotlinx.serialization.cbor/Cbor.Default.CoseCompliant|{}CoseCompliant[0]
+            final fun <get-CoseCompliant>(): kotlinx.serialization.cbor/Cbor // kotlinx.serialization.cbor/Cbor.Default.CoseCompliant.<get-CoseCompliant>|<get-CoseCompliant>(){}[0]
+    }
+    final val configuration // kotlinx.serialization.cbor/Cbor.configuration|{}configuration[0]
+        final fun <get-configuration>(): kotlinx.serialization.cbor/CborConfiguration // kotlinx.serialization.cbor/Cbor.configuration.<get-configuration>|<get-configuration>(){}[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§<kotlin.Any?>}[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§<kotlin.Any?>}[0]
     open val serializersModule // kotlinx.serialization.cbor/Cbor.serializersModule|{}serializersModule[0]
diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/Cbor.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/Cbor.kt
index 9e76a8f..21293a9 100644
--- a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/Cbor.kt
+++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/Cbor.kt
@@ -5,9 +5,6 @@
 package kotlinx.serialization.cbor
 
 import kotlinx.serialization.*
-import kotlinx.serialization.builtins.*
-import kotlinx.serialization.cbor.internal.ByteArrayInput
-import kotlinx.serialization.cbor.internal.ByteArrayOutput
 import kotlinx.serialization.cbor.internal.*
 import kotlinx.serialization.modules.*
 
@@ -18,45 +15,90 @@
  * if necessary, registered custom serializers (in [SerializersModule] provided by [serializersModule] constructor parameter).
  *
  * ### Known caveats and limitations:
- * Supports reading collections of both definite and indefinite lengths; however,
- * serialization always writes maps and lists as [indefinite-length](https://tools.ietf.org/html/rfc7049#section-2.2.1) ones.
- * Does not support [optional tags](https://tools.ietf.org/html/rfc7049#section-2.4) representing datetime, bignums, etc.
+ * Can be used to produce fully [COSE](https://datatracker.ietf.org/doc/html/rfc8812)-compliant data
+ * but canonical sorting of map keys needs to be done manually by specifying members in appropriate order.
  * Fully support CBOR maps, which, unlike JSON ones, may contain keys of non-primitive types, and may produce such maps
  * from corresponding Kotlin objects. However, other 3rd-party parsers (e.g. `jackson-dataformat-cbor`) may not accept such maps.
- *
- * @param encodeDefaults specifies whether default values of Kotlin properties are encoded.
- *                       False by default; meaning that properties with values equal to defaults will be elided.
- * @param ignoreUnknownKeys specifies if unknown CBOR elements should be ignored (skipped) when decoding.
  */
 @ExperimentalSerializationApi
 public sealed class Cbor(
-    internal val encodeDefaults: Boolean,
-    internal val ignoreUnknownKeys: Boolean,
+    public val configuration: CborConfiguration,
     override val serializersModule: SerializersModule
 ) : BinaryFormat {
 
     /**
-     * The default instance of [Cbor]
+     * The default instance of [Cbor]. Neither writes nor verifies tags. Uses indefinite length encoding by default.
      */
-    public companion object Default : Cbor(false, false, EmptySerializersModule())
+    public companion object Default :
+        Cbor(
+            CborConfiguration(
+                encodeDefaults = false,
+                ignoreUnknownKeys = false,
+                encodeKeyTags = false,
+                encodeValueTags = false,
+                encodeObjectTags = false,
+                verifyKeyTags = false,
+                verifyValueTags = false,
+                verifyObjectTags = false,
+                useDefiniteLengthEncoding = false,
+                preferCborLabelsOverNames = false,
+                alwaysUseByteString = false
+            ), EmptySerializersModule()
+        ) {
+
+        /**
+         * Preconfigured instance of [Cbor] for COSE compliance. Encodes and verifies all tags, uses definite length
+         * encoding and prefers labels to serial names. **DOES NOT** sort CBOR map keys; declare them in canonical order
+         * for full cbor compliance!
+         */
+        public val CoseCompliant: Cbor =
+            Cbor {
+                encodeDefaults = false
+                ignoreUnknownKeys = false
+                encodeKeyTags = true
+                encodeValueTags = true
+                encodeObjectTags = true
+                verifyKeyTags = true
+                verifyValueTags = true
+                verifyObjectTags = true
+                useDefiniteLengthEncoding = true
+                preferCborLabelsOverNames = true
+                alwaysUseByteString = false
+                serializersModule = EmptySerializersModule()
+            }
+    }
 
     override fun <T> encodeToByteArray(serializer: SerializationStrategy<T>, value: T): ByteArray {
         val output = ByteArrayOutput()
-        val dumper = CborWriter(this, CborEncoder(output))
+        val dumper = if (configuration.useDefiniteLengthEncoding) DefiniteLengthCborWriter(
+            this,
+            output
+        ) else IndefiniteLengthCborWriter(
+            this,
+            output
+        )
         dumper.encodeSerializableValue(serializer, value)
+
         return output.toByteArray()
+
     }
 
     override fun <T> decodeFromByteArray(deserializer: DeserializationStrategy<T>, bytes: ByteArray): T {
         val stream = ByteArrayInput(bytes)
-        val reader = CborReader(this, CborDecoder(stream))
+        val reader = CborReader(this, CborParser(stream, configuration.verifyObjectTags))
         return reader.decodeSerializableValue(deserializer)
     }
 }
 
 @OptIn(ExperimentalSerializationApi::class)
-private class CborImpl(encodeDefaults: Boolean, ignoreUnknownKeys: Boolean, serializersModule: SerializersModule) :
-    Cbor(encodeDefaults, ignoreUnknownKeys, serializersModule)
+private class CborImpl(
+    configuration: CborConfiguration,
+    serializersModule: SerializersModule
+) :
+    Cbor(
+        configuration,
+        serializersModule
+    )
 
 /**
  * Creates an instance of [Cbor] configured from the optionally given [Cbor instance][from]
@@ -66,7 +108,20 @@
 public fun Cbor(from: Cbor = Cbor, builderAction: CborBuilder.() -> Unit): Cbor {
     val builder = CborBuilder(from)
     builder.builderAction()
-    return CborImpl(builder.encodeDefaults, builder.ignoreUnknownKeys, builder.serializersModule)
+    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
+    )
 }
 
 /**
@@ -78,14 +133,115 @@
     /**
      * Specifies whether default values of Kotlin properties should be encoded.
      */
-    public var encodeDefaults: Boolean = cbor.encodeDefaults
+    public var encodeDefaults: Boolean = cbor.configuration.encodeDefaults
 
     /**
      * Specifies whether encounters of unknown properties in the input CBOR
      * should be ignored instead of throwing [SerializationException].
      * `false` by default.
      */
-    public var ignoreUnknownKeys: Boolean = cbor.ignoreUnknownKeys
+    public var ignoreUnknownKeys: Boolean = cbor.configuration.ignoreUnknownKeys
+
+    /**
+     * Specifies whether tags set using the [KeyTags] annotation should be written.
+     * CBOR allows for optionally defining *tags* for properties and their values. When this switch is set to `true` tags on
+     * CBOR map keys (i.e. properties) are encoded into the resulting byte string to transport additional information.
+     * See [RFC 8949 Tagging of Items](https://datatracker.ietf.org/doc/html/rfc8949#name-tagging-of-items) for more info.
+     */
+    public var encodeKeyTags: Boolean = cbor.configuration.encodeKeyTags
+
+    /**
+     * Specifies whether tags set using the [ValueTags] annotation should be written.
+     * CBOR allows for optionally defining *tags* for properties and their values. When this switch is set to `true`, tags on
+     * CBOR map values (i.e. the values of properties and map entries) are encoded into the resulting byte string to
+     * transport additional information. Well-known tags are specified in [CborTag].
+     * See [RFC 8949 Tagging of Items](https://datatracker.ietf.org/doc/html/rfc8949#name-tagging-of-items) for more info.
+     */
+    public var encodeValueTags: Boolean = cbor.configuration.encodeValueTags
+
+    /**
+     * Specifies whether tags set using the [ObjectTags] annotation should be written.
+     * When this switch is set to `true` , it is possible to directly declare classes to always be tagged.
+     * This then applies to isolated objects of such a tagged class being serialized and to objects of such a class used as
+     * values in a list, but also or when they are used as a property in another class.
+     * Forcing objects to always be tagged in such a manner is accomplished by the [ObjectTags] annotation,
+     * which works just as [ValueTags], but for class definitions.
+     * When serializing, object tags will always be encoded directly before to the data of the tagged object, i.e. a
+     * value-tagged property of an object-tagged type will have the value tags preceding the object tags.
+     * Well-known tags are specified in [CborTag].
+     * See [RFC 8949 Tagging of Items](https://datatracker.ietf.org/doc/html/rfc8949#name-tagging-of-items) for more info.
+     */
+    public var encodeObjectTags: Boolean = cbor.configuration.encodeObjectTags
+
+    /**
+     * Specifies whether tags preceding map keys (i.e. properties) should be matched against the
+     * [KeyTags] annotation during the deserialization process.
+     * CBOR allows for optionally defining *tags* for properties and their values. When the [encodeKeyTags] switch is set to
+     * `true` tags on CBOR map keys (i.e. properties) are encoded into the resulting byte string to transport additional
+     * information. Setting [verifyKeyTags] to `true` forces strict verification of such tags during deserialization.
+     * I.e. tags must be present on all properties of a class annotated with [KeyTags] in the CBOR byte stream
+     * **in full and in order**.
+     * See [RFC 8949 Tagging of Items](https://datatracker.ietf.org/doc/html/rfc8949#name-tagging-of-items) for more info.
+     */
+    public var verifyKeyTags: Boolean = cbor.configuration.verifyKeyTags
+
+    /**
+     * Specifies whether tags preceding values should be matched against the [ValueTags]
+     * annotation during the deserialization process.
+     * CBOR allows for optionally defining *tags* for properties and their values. When [encodeValueTags] is set to `true`,
+     * tags on CBOR map values (i.e. the values of properties and map entries) are encoded into the resulting byte string to
+     * transport additional information.
+     * Setting [verifyValueTags] to `true` forces verification of such tags during deserialization. I.e. tags must be
+     * present on all values annotated with [ValueTags] in the CBOR byte stream **in full and in order**.
+     * See also [verifyObjectTags], since a value may have both kinds of tags. [ValueTags] precede [ObjectTags] in the CBOR
+     * byte stream. [verifyValueTags] and [verifyObjectTags] can be toggled independently.
+     * Well-known tags are specified in [CborTag].
+     */
+    public var verifyValueTags: Boolean = cbor.configuration.verifyValueTags
+
+    /**
+     * Specifies whether tags preceding values should be matched against the [ObjectTags]
+     * annotation during the deserialization process. [ObjectTags] are applied when serializing classes tagged using this
+     * annotation. This applies to isolated objects of such a class and properties, whose values are of such a tagged class.
+     * [verifyValueTags] and [verifyObjectTags] can be toggled independently. Hence, it is possible to only partially verify
+     * tags on values (if only one such configuration switch is set to true). [ValueTags] precede [ObjectTags] in the CBOR
+     * byte stream.
+     * Well-known tags are specified in [CborTag].
+     */
+    public var verifyObjectTags: Boolean = cbor.configuration.verifyObjectTags
+
+    /**
+     * Specifies whether the definite length encoding should be used (as required for COSE, for example).
+     * CBOR supports two encodings for maps and arrays: definite and indefinite length encoding. kotlinx.serialization defaults
+     * to the latter, which means that a map's or array's number of elements is not encoded, but instead a terminating byte is
+     * appended after the last element.
+     * Definite length encoding, on the other hand, omits this terminating byte, but instead prepends number of elements
+     * to the contents of a map or array. This configuration switch allows for toggling between the
+     * two modes of encoding.
+     */
+    public var useDefiniteLengthEncoding: Boolean = cbor.configuration.useDefiniteLengthEncoding
+
+    /**
+     * Specifies whether to serialize element labels (i.e. Long from [CborLabel])
+     * instead of the element names (i.e. String from [SerialName]). CBOR supports keys of all types which work just as
+     * `SerialName`s.
+     * COSE restricts this again to strings and numbers and calls these restricted map keys *labels*. String labels can be
+     * assigned by using `@SerialName`, while number labels can be assigned using the [CborLabel] annotation.
+     * The [preferCborLabelsOverNames] configuration switch can be used to prefer number labels over SerialNames in case both
+     * are present for a property. This duality allows for compact representation of a type when serialized to CBOR, while
+     * keeping expressive diagnostic names when serializing to JSON.
+     */
+    public var preferCborLabelsOverNames: Boolean = cbor.configuration.preferCborLabelsOverNames
+
+    /**
+     * Specifies whether to always use the compact [ByteString] encoding when serializing
+     * or deserializing byte arrays.
+     * By default, Kotlin `ByteArray` instances are encoded as **major type 4**.
+     * When **major type 2** is desired, then the [`@ByteString`][ByteString] annotation can be used on a case-by-case
+     * basis. The [alwaysUseByteString] configuration switch allows for globally preferring **major type 2** without needing
+     * to annotate every `ByteArray` in a class hierarchy.
+     */
+    public var alwaysUseByteString: Boolean = cbor.configuration.alwaysUseByteString
 
     /**
      * Module with contextual and polymorphic serializers to be used in the resulting [Cbor] instance.
diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/CborArray.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/CborArray.kt
new file mode 100644
index 0000000..9727b60
--- /dev/null
+++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/CborArray.kt
@@ -0,0 +1,39 @@
+package kotlinx.serialization.cbor
+
+import kotlinx.serialization.*
+
+/**
+ * Encode a class as a CBOR Array (Major type 4) instead of a CBOR map.
+ *
+ * Serialization of such a class will skip element names (or labels),
+ * only encoding the values (containing explicit nulls where necessary).
+ *
+ * Example usage:
+ *
+ * ```
+ * @CborArray
+ * @Serializable
+ * data class DataClass(
+ *     val alg: Int,
+ *     val kid: String?
+ * )
+ *
+ * Cbor.encodeToByteArray(DataClass(alg = -7, kid = null))
+ * ```
+ *
+ * will produce bytes `0x8226F6`, or in diagnostic notation:
+ *
+ * ```
+ * 82    # array(2)
+ *    26 # negative(6)
+ *    F6 # primitive(22)
+ * ```
+ *
+ * This may be used to encode COSE structures, see
+ * [RFC 9052 2. Basic COSE Structure](https://www.rfc-editor.org/rfc/rfc9052#section-2).
+ *
+ */
+@SerialInfo
+@Target(AnnotationTarget.CLASS)
+@ExperimentalSerializationApi
+public annotation class CborArray
diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/CborConfiguration.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/CborConfiguration.kt
new file mode 100644
index 0000000..3d88627
--- /dev/null
+++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/CborConfiguration.kt
@@ -0,0 +1,114 @@
+/*
+ * 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.*
+
+/**
+ * Configuration of the current [Cbor] instance available through [Cbor.configuration].
+ *
+ *  * Can be used for debug purposes and for custom Cbor-specific serializers
+ *  * via [CborEncoder] and [CborDecoder].
+ *
+ * @param encodeDefaults specifies whether default values of Kotlin properties are encoded.
+ * False by default; meaning that properties with values equal to defaults will be elided.
+ * @param ignoreUnknownKeys specifies if unknown CBOR elements should be ignored (skipped) when decoding.
+ * @param encodeKeyTags Specifies whether tags set using the [KeyTags] annotation should be written.
+ * CBOR allows for optionally defining *tags* for properties and their values. When this switch is set to `true` tags on
+ * CBOR map keys (i.e. properties) are encoded into the resulting byte string to transport additional information.
+ * See [RFC 8949 Tagging of Items](https://datatracker.ietf.org/doc/html/rfc8949#name-tagging-of-items) for more info.
+ *
+ * @param encodeValueTags Specifies whether tags set using the [ValueTags] annotation should be written.
+ * CBOR allows for optionally defining *tags* for properties and their values. When this switch is set to `true`, tags on
+ * CBOR map values (i.e. the values of properties and map entries) are encoded into the resulting byte string to
+ * transport additional information. Well-known tags are specified in [CborTag].
+ * See [RFC 8949 Tagging of Items](https://datatracker.ietf.org/doc/html/rfc8949#name-tagging-of-items) for more info.
+ *
+ * @param encodeObjectTags Specifies whether tags set using the [ObjectTags] annotation should be written.
+ * When this switch is set to `true` , it is possible to directly declare classes to always be tagged.
+ * This then applies to isolated objects of such a tagged class being serialized and to objects of such a class used as
+ * values in a list, but also or when they are used as a property in another class.
+ * Forcing objects to always be tagged in such a manner is accomplished by the [ObjectTags] annotation,
+ * which works just as [ValueTags], but for class definitions.
+ * When serializing, object tags will always be encoded directly before to the data of the tagged object, i.e. a
+ * value-tagged property of an object-tagged type will have the value tags preceding the object tags.
+ * Well-known tags are specified in [CborTag].
+ * See [RFC 8949 Tagging of Items](https://datatracker.ietf.org/doc/html/rfc8949#name-tagging-of-items) for more info.
+
+ * @param verifyKeyTags Specifies whether tags preceding map keys (i.e. properties) should be matched against the
+ * [KeyTags] annotation during the deserialization process.
+ * CBOR allows for optionally defining *tags* for properties and their values. When the [encodeKeyTags] switch is set to
+ * `true` tags on CBOR map keys (i.e. properties) are encoded into the resulting byte string to transport additional
+ * information. Setting [verifyKeyTags] to `true` forces strict verification of such tags during deserialization.
+ * I.e. tags must be present on all properties of a class annotated with [KeyTags] in the CBOR byte stream
+ * **in full and in order**.
+ * See [RFC 8949 Tagging of Items](https://datatracker.ietf.org/doc/html/rfc8949#name-tagging-of-items) for more info.
+ *
+ * @param verifyValueTags Specifies whether tags preceding values should be matched against the [ValueTags]
+ * annotation during the deserialization process.
+ * CBOR allows for optionally defining *tags* for properties and their values. When [encodeValueTags] is set to `true`,
+ * tags on CBOR map values (i.e. the values of properties and map entries) are encoded into the resulting byte string to
+ * transport additional information.
+ * Setting [verifyValueTags] to `true` forces verification of such tags during deserialization. I.e. tags must be
+ * present on all values annotated with [ValueTags] in the CBOR byte stream **in full and in order**.
+ * See also [verifyObjectTags], since a value may have both kinds of tags. [ValueTags] precede [ObjectTags] in the CBOR
+ * byte stream. [verifyValueTags] and [verifyObjectTags] can be toggled independently.
+ * Well-known tags are specified in [CborTag].
+ *
+ * @param verifyObjectTags Specifies whether tags preceding values should be matched against the [ObjectTags]
+ * annotation during the deserialization process. [ObjectTags] are applied when serializing classes tagged using this
+ * annotation. This applies to isolated objects of such a class and properties, whose values are of such a tagged class.
+ * [verifyValueTags] and [verifyObjectTags] can be toggled independently. Hence, it is possible to only partially verify
+ * tags on values (if only one such configuration switch is set to true). [ValueTags] precede [ObjectTags] in the CBOR
+ * byte stream.
+ * Well-known tags are specified in [CborTag].
+ *
+ * @param useDefiniteLengthEncoding Specifies whether the definite length encoding should be used (as required for COSE, for example).
+ * CBOR supports two encodings for maps and arrays: definite and indefinite length encoding. kotlinx.serialization defaults
+ * to the latter, which means that a map's or array's number of elements is not encoded, but instead a terminating byte is
+ * appended after the last element.
+ * Definite length encoding, on the other hand, omits this terminating byte, but instead prepends number of elements
+ * to the contents of a map or array. This configuration switch allows for toggling between the
+ * two modes of encoding.
+ *
+ * @param preferCborLabelsOverNames Specifies whether to serialize element labels (i.e. Long from [CborLabel])
+ * instead of the element names (i.e. String from [SerialName]). CBOR supports keys of all types which work just as
+ * `SerialName`s.
+ * COSE restricts this again to strings and numbers and calls these restricted map keys *labels*. String labels can be
+ * assigned by using `@SerialName`, while number labels can be assigned using the [CborLabel] annotation.
+ * The [preferCborLabelsOverNames] configuration switch can be used to prefer number labels over SerialNames in case both
+ * are present for a property. This duality allows for compact representation of a type when serialized to CBOR, while
+ * keeping expressive diagnostic names when serializing to JSON.
+ *
+ * @param alwaysUseByteString Specifies whether to always use the compact [ByteString] encoding when serializing
+ * or deserializing byte arrays.
+ * By default, Kotlin `ByteArray` instances are encoded as **major type 4**.
+ * When **major type 2** is desired, then the [`@ByteString`][ByteString] annotation can be used on a case-by-case
+ * basis. The [alwaysUseByteString] configuration switch allows for globally preferring **major type 2** without needing
+ * to annotate every `ByteArray` in a class hierarchy.
+ *
+ */
+@ExperimentalSerializationApi
+public class CborConfiguration internal constructor(
+    public val encodeDefaults: Boolean,
+    public val ignoreUnknownKeys: Boolean,
+    public val encodeKeyTags: Boolean,
+    public val encodeValueTags: Boolean,
+    public val encodeObjectTags: Boolean,
+    public val verifyKeyTags: Boolean,
+    public val verifyValueTags: Boolean,
+    public val verifyObjectTags: Boolean,
+    public val useDefiniteLengthEncoding: Boolean,
+    public val preferCborLabelsOverNames: Boolean,
+    public val alwaysUseByteString: Boolean,
+) {
+    override fun toString(): String {
+        return "CborConfiguration(encodeDefaults=$encodeDefaults, ignoreUnknownKeys=$ignoreUnknownKeys, " +
+            "encodeKeyTags=$encodeKeyTags, encodeValueTags=$encodeValueTags, encodeObjectTags=$encodeObjectTags, " +
+            "verifyKeyTags=$verifyKeyTags, verifyValueTags=$verifyValueTags, verifyObjectTags=$verifyObjectTags, " +
+            "useDefiniteLengthEncoding=$useDefiniteLengthEncoding, " +
+            "preferCborLabelsOverNames=$preferCborLabelsOverNames, alwaysUseByteString=$alwaysUseByteString)"
+    }
+}
\ No newline at end of file
diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/CborDecoder.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/CborDecoder.kt
new file mode 100644
index 0000000..c30c765
--- /dev/null
+++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/CborDecoder.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.encoding.*
+
+/**
+ * This interface provides access to the current Cbor instance, so it can be properly taken into account in a
+ * custom serializer. For example, a custom serializer can decode CBOR data wrapped into a byte array using
+ * [Cbor.decodeFromByteArray] as required by some COSE structures.
+ * The actual CBOR Decoder used during deserialization implements this interface, so it is possible to cast the decoder
+ * passed to [KSerializer.deserialize] to [CborDecoder] when implementing such low-level serializers,
+ * to access configuration properties:
+ *
+ * ```kotlin
+ * override fun deserialize(decoder: Decoder): AlgorithmParameters {
+ *   if(decoder is CborDecoder){
+ *     val useDefiniteLengthEncoding = (decoder as CborDecoder).cbor.configuration.writeDefiniteLengths
+ *     // Do CBOR-specific low-level stuff
+ *   }
+ * }
+ * ```
+ */
+@ExperimentalSerializationApi
+public interface CborDecoder : Decoder {
+    /**
+     * Exposes the current [Cbor] instance and all its configuration flags. Useful for low-level custom serializers.
+     */
+    public val cbor: Cbor
+}
\ No newline at end of file
diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/CborEncoder.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/CborEncoder.kt
new file mode 100644
index 0000000..929a753
--- /dev/null
+++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/CborEncoder.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.encoding.*
+
+/**
+ * This interface provides access to the current Cbor instance, so it can be properly taken into account in a
+ * custom serializer. For example, a custom serializer can output a byte array using [Cbor.encodeToByteArray]
+ * and embed resulting data into the output, as required, by some COSE structures.
+ * The actual CBOR Encoder used during serialization implements this interface, so it is possible to cast the encoder
+ * passed to [KSerializer.serialize] to [CborEncoder] when implementing such low-level serializers,
+ * to access configuration properties:
+ *
+ * ```kotlin
+ * override fun serialize(encoder: Encoder, value: AlgorithmParameters) {
+ *   if (encoder is CborEncoder) {
+ *     val useDefiniteLengthEncoding = (encoder as CborEncoder).cbor.configuration.writeDefiniteLengths
+ *     // Do CBOR-specific low-level stuff
+ *     }
+ * }
+ * ```
+ */
+@ExperimentalSerializationApi
+public interface CborEncoder : Encoder {
+    /**
+     * Exposes the current [Cbor] instance and all its configuration flags. Useful for low-level custom serializers.
+     */
+    public val cbor: Cbor
+}
\ No newline at end of file
diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/CborLabel.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/CborLabel.kt
new file mode 100644
index 0000000..7d6255e
--- /dev/null
+++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/CborLabel.kt
@@ -0,0 +1,48 @@
+package kotlinx.serialization.cbor
+
+import kotlinx.serialization.*
+
+/**
+ * CBOR supports keys of all sorts, not just Strings.
+ * In the COSE context, these keys are called *labels* and are limited to Strings and 64-bit negative integers
+ * and 64-bit unsigned integers.
+ * Conceptually, these work just as `SerialName`s, but to also support numbers in addition to Strings, this annotation
+ * can be used.
+ *
+ * Set the `preferCborLabelsOverNames` configuration switch to prefer them over serial names in case both are present
+ * for a property.
+ *
+ * Example usage:
+ * ```
+ * @Serializable
+ * data class DataClass(
+ *     @CborLabel(1)
+ *     @SerialName("alg")
+ *     val alg: Int
+ * )
+ * ```
+ *
+ * serializing `DataClass(alg = -7)` with `Cbor { preferCborLabelsOverNames = true }` will
+ * output `0xbf0126ff`, or in diagnostic notation:
+ *
+ * ```
+ * BF    # map(*)
+ *    01 # unsigned(1)
+ *    26 # negative(6)
+ *    FF # primitive(*)
+ * ```
+ *
+ * instead of the traditional `0xbf63616c6726ff`, or in diagnostic notation:
+ *
+ * ```
+ * BF           # map(*)
+ *    63        # text(3)
+ *       616C67 # "alg"
+ *    26        # negative(6)
+ *    FF        # primitive(*)
+ * ```
+ */
+@SerialInfo
+@Target(AnnotationTarget.PROPERTY)
+@ExperimentalSerializationApi
+public annotation class CborLabel(val label: Long)
\ No newline at end of file
diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/Tags.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/Tags.kt
new file mode 100644
index 0000000..2d32058
--- /dev/null
+++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/Tags.kt
@@ -0,0 +1,113 @@
+package kotlinx.serialization.cbor
+
+import kotlinx.serialization.cbor.internal.SuppressAnimalSniffer
+import kotlinx.serialization.*
+
+/**
+ * Specifies that a property shall be tagged and the tag is serialized as CBOR major type 6: optional semantic tagging
+ * of other major types.
+ *
+ * Example usage:
+ *
+ * ```
+ * @Serializable
+ * data class Data(
+ *     @ValueTags(1337uL)
+ *     @ByteString
+ *     val a: ByteArray, // CBOR major type 6 1337(major type 2: a byte string).
+ *
+ *     @ValueTags(1234567uL)
+ *     val b: ByteArray  // CBOR major type 6 1234567(major type 4: an array of data items).
+ * )
+ * ```
+ *
+ * See [RFC 8949 3.4. Tagging of Items](https://datatracker.ietf.org/doc/html/rfc8949#name-tagging-of-items).
+ */
+@SerialInfo
+@Target(AnnotationTarget.PROPERTY)
+@ExperimentalSerializationApi
+@SuppressAnimalSniffer
+public annotation class ValueTags(@OptIn(ExperimentalUnsignedTypes::class) vararg val tags: ULong)
+
+/**
+ * Contains a set of predefined tags, named in accordance with
+ * [RFC 8949 3.4. Tagging of Items](https://datatracker.ietf.org/doc/html/rfc8949#name-tagging-of-items)
+ */
+public object CborTag{
+    public const val DATE_TIME_STANDARD: ULong = 0u;
+    public const val DATE_TIME_EPOCH: ULong = 1u;
+    public const val BIGNUM_POSITIVE: ULong = 2u;
+    public const val BIGNUM_NEGAIVE: ULong = 3u;
+    public const val DECIMAL_FRACTION: ULong = 4u;
+    public const val BIGFLOAT: ULong = 5u;
+    public const val BASE64_URL: ULong = 21u;
+    public const val BASE64: ULong = 22u;
+    public const val BASE16: ULong = 23u;
+    public const val CBOR_ENCODED_DATA: ULong = 24u;
+    public const val URI: ULong = 32u;
+    public const val STRING_BASE64_URL: ULong = 33u;
+    public const val STRING_BASE64: ULong = 34u;
+    public const val REGEX: ULong = 35u;
+    public const val MIME_MESSAGE: ULong = 36u;
+    public const val CBOR_SELF_DESCRIBE: ULong = 55799u;
+}
+
+/**
+ * Specifies that a key (i.e. a property identifier) shall be tagged and serialized as CBOR major type 6: optional
+ * semantic tagging of other major types.
+ *
+ * Example usage:
+ *
+ * ```
+ * @Serializable
+ * data class Data(
+ *     @KeyTags(34uL)
+ *     val b: Int = -1   // results in the CBOR equivalent of 34("b"): -1
+ * )
+ * ```
+ *
+ * See [RFC 8949 3.4. Tagging of Items](https://datatracker.ietf.org/doc/html/rfc8949#name-tagging-of-items).
+ */
+@SerialInfo
+@Target(AnnotationTarget.PROPERTY)
+@ExperimentalSerializationApi
+@SuppressAnimalSniffer
+public annotation class KeyTags(@OptIn(ExperimentalUnsignedTypes::class) vararg val tags: ULong)
+
+
+
+/**
+ * Specifies that an object of a class annotated using `ObjectTags` shall be tagged and serialized as
+ * CBOR major type 6: optional semantic tagging of other major types. Can be combined with [CborArray] and [ValueTags].
+ * Note that `ObjectTags` will always be encoded directly before to the data of the tagged object, i.e. a value-tagged
+ * property of an object-tagged type will have the value tags preceding the object tags.
+ *
+ * Example usage:
+ *
+ * ```
+ * @ObjectTags(1337uL)
+ * @Serializable
+ * data class ClassAsTagged(
+ *     @SerialName("alg")
+ *     val alg: Int,
+ * )
+ * ```
+ *
+ * Encoding to CBOR results in the following byte string:
+ * ```
+ * D9 0539         # tag(1337)
+ *    BF           # map(*)
+ *       63        # text(3)
+ *          616C67 # "alg"
+ *       13        # unsigned(19)
+ *       FF        # primitive(*)
+ * ```
+ *
+ * See [RFC 8949 3.4. Tagging of Items](https://datatracker.ietf.org/doc/html/rfc8949#name-tagging-of-items).
+ */
+@SerialInfo
+@Target(AnnotationTarget.CLASS)
+@ExperimentalSerializationApi
+@SuppressAnimalSniffer
+public annotation class ObjectTags(@OptIn(ExperimentalUnsignedTypes::class) vararg val tags: ULong)
+
diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Decoder.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Decoder.kt
new file mode 100644
index 0000000..f4d18b1
--- /dev/null
+++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Decoder.kt
@@ -0,0 +1,622 @@
+/*
+ * Copyright 2017-2024 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.*
+import kotlinx.serialization.modules.*
+
+internal open class CborReader(override val cbor: Cbor, protected val parser: CborParser) : AbstractDecoder(),
+    CborDecoder {
+
+    protected var size = -1
+        private set
+    protected var finiteMode = false
+        private set
+    private var readProperties: Int = 0
+
+    protected var decodeByteArrayAsByteString = false
+    protected var tags: ULongArray? = null
+
+    protected fun setSize(size: Int) {
+        if (size >= 0) {
+            finiteMode = true
+            this.size = size
+        }
+    }
+
+    override val serializersModule: SerializersModule
+        get() = cbor.serializersModule
+
+    protected open fun skipBeginToken(objectTags: ULongArray?) = setSize(parser.startMap(objectTags))
+
+    @OptIn(ExperimentalSerializationApi::class)
+    override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder {
+        val re = if (descriptor.hasArrayTag()) {
+            CborListReader(cbor, parser)
+        } else when (descriptor.kind) {
+            StructureKind.LIST, is PolymorphicKind -> CborListReader(cbor, parser)
+            StructureKind.MAP -> CborMapReader(cbor, parser)
+            else -> CborReader(cbor, parser)
+        }
+        val objectTags = if (cbor.configuration.verifyObjectTags) descriptor.getObjectTags() else null
+        re.skipBeginToken(tags?.let { if (objectTags == null) it else ulongArrayOf(*it, *objectTags) } ?: objectTags)
+        return re
+    }
+
+    override fun endStructure(descriptor: SerialDescriptor) {
+        if (!finiteMode) parser.end()
+    }
+
+    override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
+        val index = if (cbor.configuration.ignoreUnknownKeys) {
+            val knownIndex: Int
+            while (true) {
+                if (isDone()) return CompositeDecoder.DECODE_DONE
+                val (elemName, tags) = decodeElementNameWithTagsLenient(descriptor)
+                readProperties++
+
+                val index = elemName?.let { descriptor.getElementIndex(it) } ?: CompositeDecoder.UNKNOWN_NAME
+                if (index == CompositeDecoder.UNKNOWN_NAME) {
+                    parser.skipElement(tags)
+                } else {
+                    verifyKeyTags(descriptor, index, tags)
+                    knownIndex = index
+                    break
+                }
+            }
+            knownIndex
+        } else {
+            if (isDone()) return CompositeDecoder.DECODE_DONE
+            val (elemName, tags) = decodeElementNameWithTags(descriptor)
+            readProperties++
+            descriptor.getElementIndexOrThrow(elemName).also { index ->
+                verifyKeyTags(descriptor, index, tags)
+            }
+        }
+
+        decodeByteArrayAsByteString = descriptor.isByteString(index)
+        tags = if (cbor.configuration.verifyValueTags) descriptor.getValueTags(index) else null
+        return index
+    }
+
+
+    private fun decodeElementNameWithTags(descriptor: SerialDescriptor): Pair<String, ULongArray?> {
+        var (elemName, cborLabel, tags) = parser.nextTaggedStringOrNumber()
+        if (elemName == null && cborLabel != null) {
+            elemName = descriptor.getElementNameForCborLabel(cborLabel)
+                ?: throw CborDecodingException("CborLabel unknown: $cborLabel for $descriptor")
+        }
+        if (elemName == null) {
+            throw CborDecodingException("Expected (tagged) string or number, got nothing for $descriptor")
+        }
+        return elemName to tags
+    }
+
+    private fun decodeElementNameWithTagsLenient(descriptor: SerialDescriptor): Pair<String?, ULongArray?> {
+        var (elemName, cborLabel, tags) = parser.nextTaggedStringOrNumber()
+        if (elemName == null && cborLabel != null) {
+            elemName = descriptor.getElementNameForCborLabel(cborLabel)
+        }
+        return elemName to tags
+    }
+
+    @OptIn(ExperimentalSerializationApi::class)
+    override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T {
+        return if ((decodeByteArrayAsByteString || cbor.configuration.alwaysUseByteString)
+            && deserializer.descriptor == ByteArraySerializer().descriptor
+        ) {
+            @Suppress("UNCHECKED_CAST")
+            parser.nextByteString(tags) as T
+        } else {
+            decodeByteArrayAsByteString = decodeByteArrayAsByteString || deserializer.descriptor.isInlineByteString()
+            super<AbstractDecoder>.decodeSerializableValue(deserializer)
+        }
+    }
+
+    override fun decodeString() = parser.nextString(tags)
+
+    override fun decodeNotNullMark(): Boolean = !parser.isNull()
+
+    override fun decodeDouble() = parser.nextDouble(tags)
+    override fun decodeFloat() = parser.nextFloat(tags)
+
+    override fun decodeBoolean() = parser.nextBoolean(tags)
+
+    override fun decodeByte() = parser.nextNumber(tags).toByte()
+    override fun decodeShort() = parser.nextNumber(tags).toShort()
+    override fun decodeChar() = parser.nextNumber(tags).toInt().toChar()
+    override fun decodeInt() = parser.nextNumber(tags).toInt()
+    override fun decodeLong() = parser.nextNumber(tags)
+
+    override fun decodeNull() = parser.nextNull(tags)
+
+    override fun decodeEnum(enumDescriptor: SerialDescriptor): Int =
+        enumDescriptor.getElementIndexOrThrow(parser.nextString(tags))
+
+    private fun isDone(): Boolean = !finiteMode && parser.isEnd() || (finiteMode && readProperties >= size)
+
+    private fun verifyKeyTags(descriptor: SerialDescriptor, index: Int, tags: ULongArray?) {
+        if (cbor.configuration.verifyKeyTags) {
+            descriptor.getKeyTags(index)?.let { keyTags ->
+                parser.verifyTagsAndThrow(keyTags, tags)
+            }
+        }
+    }
+}
+
+internal class CborParser(private val input: ByteArrayInput, private val verifyObjectTags: Boolean) {
+    private var curByte: Int = -1
+
+    init {
+        readByte()
+    }
+
+    private fun readByte(): Int {
+        curByte = input.read()
+        return curByte
+    }
+
+    fun isEof() = curByte == -1
+
+    private fun skipByte(expected: Int) {
+        if (curByte != expected) throw CborDecodingException("byte ${printByte(expected)}", curByte)
+        readByte()
+    }
+
+    fun isNull() = (curByte == NULL || curByte == EMPTY_MAP)
+
+    fun nextNull(tags: ULongArray? = null): Nothing? {
+        processTags(tags)
+        if (curByte == NULL) {
+            skipByte(NULL)
+        } else if (curByte == EMPTY_MAP) {
+            skipByte(EMPTY_MAP)
+        }
+        return null
+    }
+
+    fun nextBoolean(tags: ULongArray? = null): Boolean {
+        processTags(tags)
+        val ans = when (curByte) {
+            TRUE -> true
+            FALSE -> false
+            else -> throw CborDecodingException("boolean value", curByte)
+        }
+        readByte()
+        return ans
+    }
+
+    fun startArray(tags: ULongArray? = null) = startSized(tags, BEGIN_ARRAY, HEADER_ARRAY, "array")
+
+    fun startMap(tags: ULongArray? = null) = startSized(tags, BEGIN_MAP, HEADER_MAP, "map")
+
+    private fun startSized(
+        tags: ULongArray?,
+        unboundedHeader: Int,
+        boundedHeaderMask: Int,
+        collectionType: String
+    ): Int {
+        processTags(tags)
+        if (curByte == unboundedHeader) {
+            skipByte(unboundedHeader)
+            return -1
+        }
+        if ((curByte and 0b111_00000) != boundedHeaderMask)
+            throw CborDecodingException("start of $collectionType", curByte)
+        val size = readNumber().toInt()
+        readByte()
+        return size
+    }
+
+    fun isEnd() = curByte == BREAK
+
+    fun end() = skipByte(BREAK)
+
+    fun nextByteString(tags: ULongArray? = null): ByteArray {
+        processTags(tags)
+        if ((curByte and 0b111_00000) != HEADER_BYTE_STRING)
+            throw CborDecodingException("start of byte string", curByte)
+        val arr = readBytes()
+        readByte()
+        return arr
+    }
+
+    fun nextString(tags: ULongArray? = null) = 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<String, ULongArray?> {
+        val collectedTags = processTags(tags)
+        if ((curByte and 0b111_00000) != HEADER_STRING)
+            throw CborDecodingException("start of string", curByte)
+        val arr = readBytes()
+        val ans = arr.decodeToString()
+        readByte()
+        return ans to collectedTags
+    }
+
+    private fun readBytes(): ByteArray =
+        if (curByte and 0b000_11111 == ADDITIONAL_INFORMATION_INDEFINITE_LENGTH) {
+            readByte()
+            readIndefiniteLengthBytes()
+        } else {
+            val strLen = readNumber().toInt()
+            input.readExactNBytes(strLen)
+        }
+
+    private fun processTags(tags: ULongArray?): ULongArray? {
+        var index = 0
+        val collectedTags = mutableListOf<ULong>()
+        while ((curByte and 0b111_00000) == HEADER_TAG) {
+            val readTag = readNumber().toULong() // This is the tag number
+            collectedTags += readTag
+            // value tags and object tags are intermingled (keyTags are always separate)
+            // so this check only holds if we verify both
+            if (verifyObjectTags) {
+                tags?.let {
+                    if (index++ >= it.size) throw CborDecodingException("More tags found than the ${it.size} tags specified")
+                }
+            }
+            readByte()
+        }
+        return (if (collectedTags.isEmpty()) null else collectedTags.toULongArray()).also { collected ->
+            //We only want to compare if tags are actually set, otherwise, we don't care
+            tags?.let {
+                if (verifyObjectTags) { //again, this check only works if we verify value tags and object tags
+                    verifyTagsAndThrow(it, collected)
+                } else {
+                    // If we don't care for object tags, the best we can do is assure that the collected tags start with
+                    // the expected tags. (yes this could co somewhere else, but putting it here groups the code nicely
+                    // into if-else branches.
+                    if ((collectedTags.size < it.size)
+                        || (collectedTags.subList(0, it.size) != it.asList())
+                    ) throw CborDecodingException("CBOR tags $collectedTags do not start with specified tags $it")
+                }
+            }
+        }
+    }
+
+    internal fun verifyTagsAndThrow(expected: ULongArray, actual: ULongArray?) {
+        if (!expected.contentEquals(actual))
+            throw CborDecodingException(
+                "CBOR tags ${actual?.contentToString()} do not match expected tags ${expected.contentToString()}"
+            )
+    }
+
+    /**
+     * Used for reading the tags and either string (element name) or number (serial label)
+     */
+    fun nextTaggedStringOrNumber(): Triple<String?, Long?, ULongArray?> {
+        val collectedTags = processTags(null)
+        if ((curByte and 0b111_00000) == HEADER_STRING) {
+            val arr = readBytes()
+            val ans = arr.decodeToString()
+            readByte()
+            return Triple(ans, null, collectedTags)
+        } else {
+            val res = readNumber()
+            readByte()
+            return Triple(null, res, collectedTags)
+        }
+    }
+
+    fun nextNumber(tags: ULongArray? = null): Long {
+        processTags(tags)
+        val res = readNumber()
+        readByte()
+        return res
+    }
+
+    private fun readNumber(): Long {
+        val value = 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
+    }
+
+    private fun ByteArrayInput.readExact(bytes: Int): Long {
+        val arr = readExactNBytes(bytes)
+        var result = 0L
+        for (i in 0 until bytes) {
+            result = (result shl 8) or (arr[i].toInt() and 0xFF).toLong()
+        }
+        return result
+    }
+
+    private fun ByteArrayInput.readExactNBytes(bytesCount: Int): ByteArray {
+        if (bytesCount > availableBytes) {
+            error("Unexpected EOF, available $availableBytes bytes, requested: $bytesCount")
+        }
+        val array = ByteArray(bytesCount)
+        read(array, 0, bytesCount)
+        return array
+    }
+
+    fun nextFloat(tags: ULongArray? = null): Float {
+        processTags(tags)
+        val res = when (curByte) {
+            NEXT_FLOAT -> Float.fromBits(readInt())
+            NEXT_HALF -> floatFromHalfBits(readShort())
+            else -> throw CborDecodingException("float header", curByte)
+        }
+        readByte()
+        return res
+    }
+
+    fun nextDouble(tag: ULong) = nextDouble(ulongArrayOf(tag))
+
+    fun nextDouble(tags: ULongArray? = null): Double {
+        processTags(tags)
+        val res = when (curByte) {
+            NEXT_DOUBLE -> Double.fromBits(readLong())
+            NEXT_FLOAT -> Float.fromBits(readInt()).toDouble()
+            NEXT_HALF -> floatFromHalfBits(readShort()).toDouble()
+            else -> throw CborDecodingException("double header", curByte)
+        }
+        readByte()
+        return res
+    }
+
+    private fun readLong(): Long {
+        var result = 0L
+        for (i in 0..7) {
+            val byte = input.read()
+            result = (result shl 8) or byte.toLong()
+        }
+        return result
+    }
+
+    private fun readShort(): Short {
+        val highByte = input.read()
+        val lowByte = input.read()
+        return (highByte shl 8 or lowByte).toShort()
+    }
+
+    private fun readInt(): Int {
+        var result = 0
+        for (i in 0..3) {
+            val byte = input.read()
+            result = (result shl 8) or byte
+        }
+        return result
+    }
+
+    /**
+     * Skips the current value element. Bytes are processed to determine the element type (and corresponding length), to
+     * determine how many bytes to skip.
+     *
+     * For primitive (finite length) elements (e.g. unsigned integer, text string), their length is read and
+     * corresponding number of bytes are skipped.
+     *
+     * For elements that contain children (e.g. array, map), the child count is read and added to a "length stack"
+     * (which represents the "number of elements" at each depth of the CBOR data structure). When a child element has
+     * 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? = null) {
+        val lengthStack = mutableListOf<Int>()
+
+        processTags(tags)
+
+        do {
+            if (isEof()) throw CborDecodingException("Unexpected EOF while skipping element")
+
+            if (isIndefinite()) {
+                lengthStack.add(LENGTH_STACK_INDEFINITE)
+            } else if (isEnd()) {
+                if (lengthStack.removeLastOrNull() != LENGTH_STACK_INDEFINITE)
+                    throw CborDecodingException("next data item", curByte)
+                prune(lengthStack)
+            } else {
+                val header = curByte and 0b111_00000
+                val length = elementLength()
+                if (header == HEADER_ARRAY || header == HEADER_MAP) {
+                    if (length > 0) lengthStack.add(length)
+                    processTags(tags)
+                } else {
+                    input.skip(length)
+                    prune(lengthStack)
+                }
+            }
+
+            readByte()
+        } while (lengthStack.isNotEmpty())
+    }
+
+    fun skipElement(singleTag: ULong?) = skipElement(singleTag?.let { ulongArrayOf(it) })
+
+    /**
+     * Removes an item from the top of the [lengthStack], cascading the removal if the item represents the last item
+     * (i.e. a length value of `1`) at its stack depth.
+     *
+     * For example, pruning a [lengthStack] of `[3, 2, 1, 1]` would result in `[3, 1]`.
+     */
+    private fun prune(lengthStack: MutableList<Int>) {
+        for (i in lengthStack.lastIndex downTo 0) {
+            when (lengthStack[i]) {
+                LENGTH_STACK_INDEFINITE -> break
+                1 -> lengthStack.removeAt(i)
+                else -> {
+                    lengthStack[i] = lengthStack[i] - 1
+                    break
+                }
+            }
+        }
+    }
+
+    /**
+     * Determines if [curByte] represents an indefinite length CBOR item.
+     *
+     * Per [RFC 7049: 2.2. Indefinite Lengths for Some Major Types](https://tools.ietf.org/html/rfc7049#section-2.2):
+     * > Four CBOR items (arrays, maps, byte strings, and text strings) can be encoded with an indefinite length
+     */
+    private fun isIndefinite(): Boolean {
+        val majorType = curByte and 0b111_00000
+        val value = curByte and 0b000_11111
+
+        return value == ADDITIONAL_INFORMATION_INDEFINITE_LENGTH &&
+            (majorType == HEADER_ARRAY || majorType == HEADER_MAP ||
+                majorType == HEADER_BYTE_STRING || majorType == HEADER_STRING)
+    }
+
+    /**
+     * Determines the length of the CBOR item represented by [curByte]; length has specific meaning based on the type:
+     *
+     * | Major type          | Length represents number of... |
+     * |---------------------|--------------------------------|
+     * | 0. unsigned integer | bytes                          |
+     * | 1. negative integer | bytes                          |
+     * | 2. byte string      | bytes                          |
+     * | 3. string           | bytes                          |
+     * | 4. array            | data items (values)            |
+     * | 5. map              | sub-items (keys + values)      |
+     * | 6. tag              | bytes                          |
+     */
+    private fun elementLength(): Int {
+        val majorType = curByte and 0b111_00000
+        val additionalInformation = curByte and 0b000_11111
+
+        return when (majorType) {
+            HEADER_BYTE_STRING, HEADER_STRING, HEADER_ARRAY -> readNumber().toInt()
+            HEADER_MAP -> readNumber().toInt() * 2
+            else -> when (additionalInformation) {
+                24 -> 1
+                25 -> 2
+                26 -> 4
+                27 -> 8
+                else -> 0
+            }
+        }
+    }
+
+    /**
+     * Indefinite-length byte sequences contain an unknown number of fixed-length byte sequences (chunks).
+     *
+     * @return [ByteArray] containing all of the concatenated bytes found in the buffer.
+     */
+    private fun readIndefiniteLengthBytes(): ByteArray {
+        val byteStrings = mutableListOf<ByteArray>()
+        do {
+            byteStrings.add(readBytes())
+            readByte()
+        } while (!isEnd())
+        return byteStrings.flatten()
+    }
+}
+
+private fun Iterable<ByteArray>.flatten(): ByteArray {
+    val output = ByteArray(sumOf { it.size })
+    var position = 0
+    for (chunk in this) {
+        chunk.copyInto(output, position)
+        position += chunk.size
+    }
+
+    return output
+}
+
+
+private class CborMapReader(cbor: Cbor, decoder: CborParser) : 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 var ind = 0
+
+    override fun skipBeginToken(objectTags: ULongArray?) =
+        setSize(parser.startArray(tags?.let { if (objectTags == null) it else ulongArrayOf(*it, *objectTags) }
+            ?: objectTags))
+
+    override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
+        return if (!finiteMode && parser.isEnd() || (finiteMode && ind >= size)) CompositeDecoder.DECODE_DONE else
+            ind++.also {
+                decodeByteArrayAsByteString = descriptor.isByteString(it)
+            }
+    }
+}
+
+
+private val normalizeBaseBits = SINGLE_PRECISION_NORMALIZE_BASE.toBits()
+
+
+/*
+ * For details about half-precision floating-point numbers see https://tools.ietf.org/html/rfc7049#appendix-D
+ */
+private fun floatFromHalfBits(bits: Short): Float {
+    val intBits = bits.toInt()
+
+    val negative = (intBits and 0x8000) != 0
+    val halfExp = intBits shr 10 and HALF_PRECISION_MAX_EXPONENT
+    val halfMant = intBits and HALF_PRECISION_MAX_MANTISSA
+
+    val exp: Int
+    val mant: Int
+
+    when (halfExp) {
+        HALF_PRECISION_MAX_EXPONENT -> {
+            // if exponent maximal - value is NaN or Infinity
+            exp = SINGLE_PRECISION_MAX_EXPONENT
+            mant = halfMant
+        }
+
+        0 -> {
+            if (halfMant == 0) {
+                // if exponent and mantissa are zero - value is zero
+                mant = 0
+                exp = 0
+            } else {
+                // if exponent is zero and mantissa non-zero - value denormalized. normalize it
+                var res = Float.fromBits(normalizeBaseBits + halfMant)
+                res -= SINGLE_PRECISION_NORMALIZE_BASE
+                return if (negative) -res else res
+            }
+        }
+
+        else -> {
+            // normalized value
+            exp = (halfExp + (SINGLE_PRECISION_EXPONENT_BIAS - HALF_PRECISION_EXPONENT_BIAS))
+            mant = halfMant
+        }
+    }
+
+    val res = Float.fromBits((exp shl 23) or (mant shl 13))
+    return if (negative) -res else res
+}
+
+
+@OptIn(ExperimentalSerializationApi::class)
+private fun SerialDescriptor.getElementNameForCborLabel(label: Long): String? {
+    return elementNames.firstOrNull { getCborLabel(getElementIndex(it)) == label }
+}
+
+
+@OptIn(ExperimentalSerializationApi::class)
+private fun SerialDescriptor.getElementIndexOrThrow(name: String): Int {
+    val index = getElementIndex(name)
+    if (index == CompositeDecoder.UNKNOWN_NAME)
+        throw SerializationException(
+            "$serialName does not contain element with name '$name." +
+                " You can enable 'CborBuilder.ignoreUnknownKeys' property to ignore unknown keys"
+        )
+    return index
+}
diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Encoder.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Encoder.kt
new file mode 100644
index 0000000..eb5fc55
--- /dev/null
+++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Encoder.kt
@@ -0,0 +1,332 @@
+/*
+ * Copyright 2017-2024 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.*
+import kotlinx.serialization.modules.*
+import kotlin.experimental.*
+
+
+//value classes are only inlined on the JVM, so we use a typealias and extensions instead
+private typealias Stack = MutableList<CborWriter.Data>
+
+private fun Stack(initial: CborWriter.Data): Stack = mutableListOf(initial)
+private fun Stack.push(value: CborWriter.Data) = add(value)
+private fun Stack.pop() = removeLast()
+private fun Stack.peek() = last()
+
+// Writes class as map [fieldName, fieldValue]
+// Split implementation to optimize base case
+internal sealed class CborWriter(
+    override val cbor: Cbor,
+    protected val output: ByteArrayOutput,
+) : AbstractEncoder(), CborEncoder {
+    protected var isClass = false
+
+    protected var encodeByteArrayAsByteString = false
+
+    class Data(val bytes: ByteArrayOutput, var elementCount: Int)
+
+    protected abstract fun getDestination(): ByteArrayOutput
+
+    override val serializersModule: SerializersModule
+        get() = cbor.serializersModule
+
+
+    @OptIn(ExperimentalSerializationApi::class)
+    override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) {
+
+        if ((encodeByteArrayAsByteString || cbor.configuration.alwaysUseByteString)
+            && serializer.descriptor == ByteArraySerializer().descriptor
+        ) {
+            getDestination().encodeByteString(value as ByteArray)
+        } else {
+            encodeByteArrayAsByteString = encodeByteArrayAsByteString || serializer.descriptor.isInlineByteString()
+            super<AbstractEncoder>.encodeSerializableValue(serializer, value)
+        }
+    }
+
+    override fun shouldEncodeElementDefault(descriptor: SerialDescriptor, index: Int): Boolean =
+        cbor.configuration.encodeDefaults
+
+    protected abstract fun incrementChildren()
+
+    override fun encodeString(value: String) {
+        getDestination().encodeString(value)
+    }
+
+
+    override fun encodeFloat(value: Float) {
+        getDestination().encodeFloat(value)
+    }
+
+
+    override fun encodeDouble(value: Double) {
+        getDestination().encodeDouble(value)
+    }
+
+
+    override fun encodeChar(value: Char) {
+        getDestination().encodeNumber(value.code.toLong())
+    }
+
+
+    override fun encodeByte(value: Byte) {
+        getDestination().encodeNumber(value.toLong())
+    }
+
+
+    override fun encodeShort(value: Short) {
+        getDestination().encodeNumber(value.toLong())
+    }
+
+    override fun encodeInt(value: Int) {
+        getDestination().encodeNumber(value.toLong())
+    }
+
+
+    override fun encodeLong(value: Long) {
+        getDestination().encodeNumber(value)
+    }
+
+
+    override fun encodeBoolean(value: Boolean) {
+        getDestination().encodeBoolean(value)
+    }
+
+
+    override fun encodeNull() {
+        if (isClass) getDestination().encodeEmptyMap()
+        else getDestination().encodeNull()
+    }
+
+    @OptIn(ExperimentalSerializationApi::class) // KT-46731
+    override fun encodeEnum(
+        enumDescriptor: SerialDescriptor,
+        index: Int
+    ) {
+        getDestination().encodeString(enumDescriptor.getElementName(index))
+    }
+
+    override fun encodeElement(descriptor: SerialDescriptor, index: Int): Boolean {
+        val destination = getDestination()
+        isClass = descriptor.getElementDescriptor(index).kind == StructureKind.CLASS
+        encodeByteArrayAsByteString = descriptor.isByteString(index)
+
+        val name = descriptor.getElementName(index)
+
+
+        if (!descriptor.hasArrayTag()) {
+            if (cbor.configuration.encodeKeyTags) descriptor.getKeyTags(index)?.forEach { destination.encodeTag(it) }
+
+            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) {
+                    destination.encodeNumber(cborLabel)
+                } else {
+                    destination.encodeString(name)
+                }
+            }
+        }
+
+        if (cbor.configuration.encodeValueTags) {
+            descriptor.getValueTags(index)?.forEach { destination.encodeTag(it) }
+        }
+        incrementChildren() // needed for definite len encoding, NOOP for indefinite length encoding
+        return true
+    }
+}
+
+
+// optimized indefinite length encoder
+internal class IndefiniteLengthCborWriter(cbor: Cbor, output: ByteArrayOutput) : CborWriter(
+    cbor, output
+) {
+
+    override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder {
+        if (cbor.configuration.encodeObjectTags) descriptor.getObjectTags()?.forEach {
+            output.encodeTag(it)
+        }
+        if (descriptor.hasArrayTag()) {
+            output.startArray()
+        } else {
+            when (descriptor.kind) {
+                StructureKind.LIST, is PolymorphicKind -> output.startArray()
+                is StructureKind.MAP -> output.startMap()
+                else -> output.startMap()
+            }
+        }
+        return this
+    }
+
+    override fun endStructure(descriptor: SerialDescriptor) {
+        output.end()
+    }
+
+    override fun getDestination(): ByteArrayOutput = output
+
+
+    override fun incrementChildren() {/*NOOP*/
+    }
+
+}
+
+//optimized definite length encoder
+internal class DefiniteLengthCborWriter(cbor: Cbor, output: ByteArrayOutput) : CborWriter(cbor, output) {
+
+    private val structureStack = Stack(Data(output, -1))
+    override fun getDestination(): ByteArrayOutput =
+        structureStack.peek().bytes
+
+
+    override fun incrementChildren() {
+        structureStack.peek().elementCount++
+    }
+
+    override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder {
+        val current = Data(ByteArrayOutput(), 0)
+        structureStack.push(current)
+        return this
+    }
+
+    override fun endStructure(descriptor: SerialDescriptor) {
+        val completedCurrent = structureStack.pop()
+
+        val accumulator = getDestination()
+
+        val numChildren = completedCurrent.elementCount
+
+        if (cbor.configuration.encodeObjectTags) descriptor.getObjectTags()?.forEach {
+            accumulator.encodeTag(it)
+        }
+
+        if (descriptor.hasArrayTag()) {
+            accumulator.startArray(numChildren.toULong())
+        } else {
+            when (descriptor.kind) {
+                StructureKind.LIST, is PolymorphicKind -> accumulator.startArray(numChildren.toULong())
+                is StructureKind.MAP -> accumulator.startMap((numChildren / 2).toULong())
+                else -> accumulator.startMap((numChildren).toULong())
+            }
+        }
+        accumulator.copyFrom(completedCurrent.bytes)
+    }
+}
+
+
+private fun ByteArrayOutput.startArray() = write(BEGIN_ARRAY)
+
+private fun ByteArrayOutput.startArray(size: ULong) {
+    composePositiveInline(size, HEADER_ARRAY)
+}
+
+private fun ByteArrayOutput.startMap() = write(BEGIN_MAP)
+
+private fun ByteArrayOutput.startMap(size: ULong) {
+    composePositiveInline(size, HEADER_MAP)
+}
+
+private fun ByteArrayOutput.encodeTag(tag: ULong) {
+    composePositiveInline(tag, HEADER_TAG)
+}
+
+internal fun ByteArrayOutput.end() = write(BREAK)
+
+internal fun ByteArrayOutput.encodeNull() = write(NULL)
+
+internal fun ByteArrayOutput.encodeEmptyMap() = write(EMPTY_MAP)
+
+internal fun ByteArrayOutput.writeByte(byteValue: Int) = write(byteValue)
+
+internal fun ByteArrayOutput.encodeBoolean(value: Boolean) = write(if (value) TRUE else FALSE)
+
+internal fun ByteArrayOutput.encodeNumber(value: Long) = write(composeNumber(value))
+
+internal fun ByteArrayOutput.encodeByteString(data: ByteArray) {
+    this.encodeByteArray(data, HEADER_BYTE_STRING)
+}
+
+internal fun ByteArrayOutput.encodeString(value: String) {
+    this.encodeByteArray(value.encodeToByteArray(), HEADER_STRING)
+}
+
+internal fun ByteArrayOutput.encodeByteArray(data: ByteArray, type: Int) {
+    composePositiveInline(data.size.toULong(), type)
+    write(data)
+}
+
+internal fun ByteArrayOutput.encodeFloat(value: Float) {
+    write(NEXT_FLOAT)
+    val bits = value.toRawBits()
+    for (i in 0..3) {
+        write((bits shr (24 - 8 * i)) and 0xFF)
+    }
+}
+
+internal fun ByteArrayOutput.encodeDouble(value: Double) {
+    write(NEXT_DOUBLE)
+    val bits = value.toRawBits()
+    for (i in 0..7) {
+        write(((bits shr (56 - 8 * i)) and 0xFF).toInt())
+    }
+}
+
+//don't know why, but if the negative branch is also optimized and everything operates directly on the ByteArrayOutput it gets slower
+private fun composeNumber(value: Long): ByteArray =
+    if (value >= 0) composePositive(value.toULong()) else composeNegative(value)
+
+private fun ByteArrayOutput.composePositiveInline(value: ULong, mod: Int) = when (value) {
+    in 0u..23u -> writeByte(value.toInt() or mod)
+    in 24u..UByte.MAX_VALUE.toUInt() -> {
+        writeByte(24 or mod)
+        writeByte(value.toInt())
+    }
+
+    in (UByte.MAX_VALUE.toUInt() + 1u)..UShort.MAX_VALUE.toUInt() -> encodeToInline(value, 2, 25 or mod)
+    in (UShort.MAX_VALUE.toUInt() + 1u)..UInt.MAX_VALUE -> encodeToInline(value, 4, 26 or mod)
+    else -> encodeToInline(value, 8, 27 or mod)
+}
+
+
+private fun composePositive(value: ULong): ByteArray = when (value) {
+    in 0u..23u -> byteArrayOf(value.toByte())
+    in 24u..UByte.MAX_VALUE.toUInt() -> byteArrayOf(24, value.toByte())
+    in (UByte.MAX_VALUE.toUInt() + 1u)..UShort.MAX_VALUE.toUInt() -> encodeToByteArray(value, 2, 25)
+    in (UShort.MAX_VALUE.toUInt() + 1u)..UInt.MAX_VALUE -> encodeToByteArray(value, 4, 26)
+    else -> encodeToByteArray(value, 8, 27)
+}
+
+
+private fun ByteArrayOutput.encodeToInline(value: ULong, bytes: Int, tag: Int) {
+    val limit = bytes * 8 - 8
+    writeByte(tag)
+    for (i in 0 until bytes) {
+        writeByte(((value shr (limit - 8 * i)) and 0xFFu).toInt())
+    }
+}
+
+private fun encodeToByteArray(value: ULong, bytes: Int, tag: Byte): ByteArray {
+    val result = ByteArray(bytes + 1)
+    val limit = bytes * 8 - 8
+    result[0] = tag
+    for (i in 0 until bytes) {
+        result[i + 1] = ((value shr (limit - 8 * i)) and 0xFFu).toByte()
+    }
+    return result
+}
+
+private fun composeNegative(value: Long): ByteArray {
+    val aVal = if (value == Long.MIN_VALUE) Long.MAX_VALUE else -1 - value
+    val data = composePositive(aVal.toULong())
+    data[0] = data[0] or HEADER_NEGATIVE
+    return data
+}
+
diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Encoding.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Encoding.kt
index b77a18c..63b8f0a 100644
--- a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Encoding.kt
+++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Encoding.kt
@@ -1,691 +1,80 @@
 /*
  * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
  */
-@file:OptIn(ExperimentalSerializationApi::class)
+@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.*
-import kotlinx.serialization.modules.*
-import kotlin.experimental.*
 
-private const val FALSE = 0xf4
-private const val TRUE = 0xf5
-private const val NULL = 0xf6
+internal const val FALSE = 0xf4
+internal const val TRUE = 0xf5
+internal const val NULL = 0xf6
+internal const val EMPTY_MAP = 0xa0
 
-private const val NEXT_HALF = 0xf9
-private const val NEXT_FLOAT = 0xfa
-private const val NEXT_DOUBLE = 0xfb
+internal const val NEXT_HALF = 0xf9
+internal const val NEXT_FLOAT = 0xfa
+internal const val NEXT_DOUBLE = 0xfb
 
-private const val BEGIN_ARRAY = 0x9f
-private const val BEGIN_MAP = 0xbf
-private const val BREAK = 0xff
+internal const val BEGIN_ARRAY = 0x9f
+internal const val BEGIN_MAP = 0xbf
+internal const val BREAK = 0xff
 
-private const val ADDITIONAL_INFORMATION_INDEFINITE_LENGTH = 0x1f
+internal const val ADDITIONAL_INFORMATION_INDEFINITE_LENGTH = 0x1f
 
-private const val HEADER_BYTE_STRING: Byte = 0b010_00000
-private const val HEADER_STRING: Byte = 0b011_00000
-private const val HEADER_NEGATIVE: Byte = 0b001_00000
-private const val HEADER_ARRAY: Int = 0b100_00000
-private const val HEADER_MAP: Int = 0b101_00000
-private const val HEADER_TAG: Int = 0b110_00000
+internal const val HEADER_BYTE_STRING: Int = 0b010_00000
+internal const val HEADER_STRING: Int = 0b011_00000
+internal const val HEADER_NEGATIVE: Byte = 0b001_00000
+internal const val HEADER_ARRAY: Int = 0b100_00000
+internal const val HEADER_MAP: Int = 0b101_00000
+internal const val HEADER_TAG: Int = 0b110_00000
 
 /** Value to represent an indefinite length CBOR item within a "length stack". */
-private const val LENGTH_STACK_INDEFINITE = -1
+internal const val LENGTH_STACK_INDEFINITE = -1
 
-private const val HALF_PRECISION_EXPONENT_BIAS = 15
-private const val HALF_PRECISION_MAX_EXPONENT = 0x1f
-private const val HALF_PRECISION_MAX_MANTISSA = 0x3ff
+internal const val HALF_PRECISION_EXPONENT_BIAS = 15
+internal const val HALF_PRECISION_MAX_EXPONENT = 0x1f
+internal const val HALF_PRECISION_MAX_MANTISSA = 0x3ff
 
-private const val SINGLE_PRECISION_EXPONENT_BIAS = 127
-private const val SINGLE_PRECISION_MAX_EXPONENT = 0xFF
+internal const val SINGLE_PRECISION_EXPONENT_BIAS = 127
+internal const val SINGLE_PRECISION_MAX_EXPONENT = 0xFF
 
-private const val SINGLE_PRECISION_NORMALIZE_BASE = 0.5f
-
-// Differs from List only in start byte
-private class CborMapWriter(cbor: Cbor, encoder: CborEncoder) : CborListWriter(cbor, encoder) {
-    override fun writeBeginToken() = encoder.startMap()
-}
-
-// Writes all elements consequently, except size - CBOR supports maps and arrays of indefinite length
-private open class CborListWriter(cbor: Cbor, encoder: CborEncoder) : CborWriter(cbor, encoder) {
-    override fun writeBeginToken() = encoder.startArray()
-
-    override fun encodeElement(descriptor: SerialDescriptor, index: Int): Boolean = true
-}
-
-// Writes class as map [fieldName, fieldValue]
-internal open class CborWriter(private val cbor: Cbor, protected val encoder: CborEncoder) : AbstractEncoder() {
-    override val serializersModule: SerializersModule
-        get() = cbor.serializersModule
-
-    private var encodeByteArrayAsByteString = false
-
-    @OptIn(ExperimentalSerializationApi::class)
-    override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) {
-        if (encodeByteArrayAsByteString && serializer.descriptor == ByteArraySerializer().descriptor) {
-            encoder.encodeByteString(value as ByteArray)
-        } else {
-            encodeByteArrayAsByteString = encodeByteArrayAsByteString || serializer.descriptor.isInlineByteString()
-
-            super.encodeSerializableValue(serializer, value)
-        }
-    }
-
-    override fun shouldEncodeElementDefault(descriptor: SerialDescriptor, index: Int): Boolean = cbor.encodeDefaults
-
-    protected open fun writeBeginToken() = encoder.startMap()
-
-    //todo: Write size of map or array if known
-    @OptIn(ExperimentalSerializationApi::class)
-    override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder {
-        val writer = when (descriptor.kind) {
-            StructureKind.LIST, is PolymorphicKind -> CborListWriter(cbor, encoder)
-            StructureKind.MAP -> CborMapWriter(cbor, encoder)
-            else -> CborWriter(cbor, encoder)
-        }
-        writer.writeBeginToken()
-        return writer
-    }
-
-    override fun endStructure(descriptor: SerialDescriptor) = encoder.end()
-
-    override fun encodeElement(descriptor: SerialDescriptor, index: Int): Boolean {
-        encodeByteArrayAsByteString = descriptor.isByteString(index)
-        val name = descriptor.getElementName(index)
-        encoder.encodeString(name)
-        return true
-    }
-
-    override fun encodeString(value: String) = encoder.encodeString(value)
-
-    override fun encodeFloat(value: Float) = encoder.encodeFloat(value)
-    override fun encodeDouble(value: Double) = encoder.encodeDouble(value)
-
-    override fun encodeChar(value: Char) = encoder.encodeNumber(value.code.toLong())
-    override fun encodeByte(value: Byte) = encoder.encodeNumber(value.toLong())
-    override fun encodeShort(value: Short) = encoder.encodeNumber(value.toLong())
-    override fun encodeInt(value: Int) = encoder.encodeNumber(value.toLong())
-    override fun encodeLong(value: Long) = encoder.encodeNumber(value)
-
-    override fun encodeBoolean(value: Boolean) = encoder.encodeBoolean(value)
-
-    override fun encodeNull() = encoder.encodeNull()
-
-    @OptIn(ExperimentalSerializationApi::class) // KT-46731
-    override fun encodeEnum(
-        enumDescriptor: SerialDescriptor,
-        index: Int
-    ) =
-        encoder.encodeString(enumDescriptor.getElementName(index))
-}
-
-// For details of representation, see https://tools.ietf.org/html/rfc7049#section-2.1
-internal class CborEncoder(private val output: ByteArrayOutput) {
-
-    fun startArray() = output.write(BEGIN_ARRAY)
-    fun startMap() = output.write(BEGIN_MAP)
-    fun end() = output.write(BREAK)
-
-    fun encodeNull() = output.write(NULL)
-
-    fun encodeBoolean(value: Boolean) = output.write(if (value) TRUE else FALSE)
-
-    fun encodeNumber(value: Long) = output.write(composeNumber(value))
-
-    fun encodeByteString(data: ByteArray) {
-        encodeByteArray(data, HEADER_BYTE_STRING)
-    }
-
-    fun encodeString(value: String) {
-        encodeByteArray(value.encodeToByteArray(), HEADER_STRING)
-    }
-
-    private fun encodeByteArray(data: ByteArray, type: Byte) {
-        val header = composeNumber(data.size.toLong())
-        header[0] = header[0] or type
-        output.write(header)
-        output.write(data)
-    }
-
-    fun encodeFloat(value: Float) {
-        output.write(NEXT_FLOAT)
-        val bits = value.toRawBits()
-        for (i in 0..3) {
-            output.write((bits shr (24 - 8 * i)) and 0xFF)
-        }
-    }
-
-    fun encodeDouble(value: Double) {
-        output.write(NEXT_DOUBLE)
-        val bits = value.toRawBits()
-        for (i in 0..7) {
-            output.write(((bits shr (56 - 8 * i)) and 0xFF).toInt())
-        }
-    }
-
-    private fun composeNumber(value: Long): ByteArray =
-        if (value >= 0) composePositive(value.toULong()) else composeNegative(value)
-
-    private fun composePositive(value: ULong): ByteArray = when (value) {
-        in 0u..23u -> byteArrayOf(value.toByte())
-        in 24u..UByte.MAX_VALUE.toUInt() -> byteArrayOf(24, value.toByte())
-        in (UByte.MAX_VALUE.toUInt() + 1u)..UShort.MAX_VALUE.toUInt() -> encodeToByteArray(value, 2, 25)
-        in (UShort.MAX_VALUE.toUInt() + 1u)..UInt.MAX_VALUE -> encodeToByteArray(value, 4, 26)
-        else -> encodeToByteArray(value, 8, 27)
-    }
-
-    private fun encodeToByteArray(value: ULong, bytes: Int, tag: Byte): ByteArray {
-        val result = ByteArray(bytes + 1)
-        val limit = bytes * 8 - 8
-        result[0] = tag
-        for (i in 0 until bytes) {
-            result[i + 1] = ((value shr (limit - 8 * i)) and 0xFFu).toByte()
-        }
-        return result
-    }
-
-    private fun composeNegative(value: Long): ByteArray {
-        val aVal = if (value == Long.MIN_VALUE) Long.MAX_VALUE else -1 - value
-        val data = composePositive(aVal.toULong())
-        data[0] = data[0] or HEADER_NEGATIVE
-        return data
-    }
-}
-
-private class CborMapReader(cbor: Cbor, decoder: CborDecoder) : CborListReader(cbor, decoder) {
-    override fun skipBeginToken() = setSize(decoder.startMap() * 2)
-}
-
-private open class CborListReader(cbor: Cbor, decoder: CborDecoder) : CborReader(cbor, decoder) {
-    private var ind = 0
-
-    override fun skipBeginToken() = setSize(decoder.startArray())
-
-    override fun decodeElementIndex(descriptor: SerialDescriptor) = if (!finiteMode && decoder.isEnd() || (finiteMode && ind >= size)) CompositeDecoder.DECODE_DONE else ind++
-}
-
-internal open class CborReader(private val cbor: Cbor, protected val decoder: CborDecoder) : AbstractDecoder() {
-
-    protected var size = -1
-        private set
-    protected var finiteMode = false
-        private set
-    private var readProperties: Int = 0
-
-    private var decodeByteArrayAsByteString = false
-
-    protected fun setSize(size: Int) {
-        if (size >= 0) {
-            finiteMode = true
-            this.size = size
-        }
-    }
-
-    override val serializersModule: SerializersModule
-        get() = cbor.serializersModule
-
-    protected open fun skipBeginToken() = setSize(decoder.startMap())
-
-    @OptIn(ExperimentalSerializationApi::class)
-    override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder {
-        val re = when (descriptor.kind) {
-            StructureKind.LIST, is PolymorphicKind -> CborListReader(cbor, decoder)
-            StructureKind.MAP -> CborMapReader(cbor, decoder)
-            else -> CborReader(cbor, decoder)
-        }
-        re.skipBeginToken()
-        return re
-    }
-
-    override fun endStructure(descriptor: SerialDescriptor) {
-        if (!finiteMode) decoder.end()
-    }
-
-    override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
-        val index = if (cbor.ignoreUnknownKeys) {
-            val knownIndex: Int
-            while (true) {
-                if (isDone()) return CompositeDecoder.DECODE_DONE
-                val elemName = decoder.nextString()
-                readProperties++
-
-                val index = descriptor.getElementIndex(elemName)
-                if (index == CompositeDecoder.UNKNOWN_NAME) {
-                    decoder.skipElement()
-                } else {
-                    knownIndex = index
-                    break
-                }
-            }
-            knownIndex
-        } else {
-            if (isDone()) return CompositeDecoder.DECODE_DONE
-            val elemName = decoder.nextString()
-            readProperties++
-            descriptor.getElementIndexOrThrow(elemName)
-        }
-
-        decodeByteArrayAsByteString = descriptor.isByteString(index)
-        return index
-    }
-
-    @OptIn(ExperimentalSerializationApi::class)
-    override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T {
-        return if (decodeByteArrayAsByteString && deserializer.descriptor == ByteArraySerializer().descriptor) {
-            @Suppress("UNCHECKED_CAST")
-            decoder.nextByteString() as T
-        } else {
-            decodeByteArrayAsByteString = decodeByteArrayAsByteString || deserializer.descriptor.isInlineByteString()
-            super.decodeSerializableValue(deserializer)
-        }
-    }
-
-    override fun decodeString() = decoder.nextString()
-
-    override fun decodeNotNullMark(): Boolean = !decoder.isNull()
-
-    override fun decodeDouble() = decoder.nextDouble()
-    override fun decodeFloat() = decoder.nextFloat()
-
-    override fun decodeBoolean() = decoder.nextBoolean()
-
-    override fun decodeByte() = decoder.nextNumber().toByte()
-    override fun decodeShort() = decoder.nextNumber().toShort()
-    override fun decodeChar() = decoder.nextNumber().toInt().toChar()
-    override fun decodeInt() = decoder.nextNumber().toInt()
-    override fun decodeLong() = decoder.nextNumber()
-
-    override fun decodeNull() = decoder.nextNull()
-
-    override fun decodeEnum(enumDescriptor: SerialDescriptor): Int =
-        enumDescriptor.getElementIndexOrThrow(decoder.nextString())
-
-    private fun isDone(): Boolean = !finiteMode && decoder.isEnd() || (finiteMode && readProperties >= size)
-}
-
-internal class CborDecoder(private val input: ByteArrayInput) {
-    private var curByte: Int = -1
-
-    init {
-        readByte()
-    }
-
-    private fun readByte(): Int {
-        curByte = input.read()
-        return curByte
-    }
-
-    fun isEof() = curByte == -1
-
-    private fun skipByte(expected: Int) {
-        if (curByte != expected) throw CborDecodingException("byte ${printByte(expected)}", curByte)
-        readByte()
-    }
-
-    fun isNull() = curByte == NULL
-
-    fun nextNull(): Nothing? {
-        skipOverTags()
-        skipByte(NULL)
-        return null
-    }
-
-    fun nextBoolean(): Boolean {
-        skipOverTags()
-        val ans = when (curByte) {
-            TRUE -> true
-            FALSE -> false
-            else -> throw CborDecodingException("boolean value", curByte)
-        }
-        readByte()
-        return ans
-    }
-
-    fun startArray() = startSized(BEGIN_ARRAY, HEADER_ARRAY, "array")
-
-    fun startMap() = startSized(BEGIN_MAP, HEADER_MAP, "map")
-
-    private fun startSized(unboundedHeader: Int, boundedHeaderMask: Int, collectionType: String): Int {
-        skipOverTags()
-        if (curByte == unboundedHeader) {
-            skipByte(unboundedHeader)
-            return -1
-        }
-        if ((curByte and 0b111_00000) != boundedHeaderMask)
-            throw CborDecodingException("start of $collectionType", curByte)
-        val size = readNumber().toInt()
-        readByte()
-        return size
-    }
-
-    fun isEnd() = curByte == BREAK
-
-    fun end() = skipByte(BREAK)
-
-    fun nextByteString(): ByteArray {
-        skipOverTags()
-        if ((curByte and 0b111_00000) != HEADER_BYTE_STRING.toInt())
-            throw CborDecodingException("start of byte string", curByte)
-        val arr = readBytes()
-        readByte()
-        return arr
-    }
-
-    fun nextString(): String {
-        skipOverTags()
-        if ((curByte and 0b111_00000) != HEADER_STRING.toInt())
-            throw CborDecodingException("start of string", curByte)
-        val arr = readBytes()
-        val ans = arr.decodeToString()
-        readByte()
-        return ans
-    }
-
-    private fun readBytes(): ByteArray =
-        if (curByte and 0b000_11111 == ADDITIONAL_INFORMATION_INDEFINITE_LENGTH) {
-            readByte()
-            readIndefiniteLengthBytes()
-        } else {
-            val strLen = readNumber().toInt()
-            input.readExactNBytes(strLen)
-        }
-
-    private fun skipOverTags() {
-        while ((curByte and 0b111_00000) == HEADER_TAG) {
-            readNumber() // This is the tag number
-            readByte()
-        }
-    }
-
-    fun nextNumber(): Long {
-        skipOverTags()
-        val res = readNumber()
-        readByte()
-        return res
-    }
-
-    private fun readNumber(): Long {
-        val value = 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
-    }
-
-    private fun ByteArrayInput.readExact(bytes: Int): Long {
-        val arr = readExactNBytes(bytes)
-        var result = 0L
-        for (i in 0 until bytes) {
-            result = (result shl 8) or (arr[i].toInt() and 0xFF).toLong()
-        }
-        return result
-    }
-
-    private fun ByteArrayInput.readExactNBytes(bytesCount: Int): ByteArray {
-        if (bytesCount > availableBytes) {
-            error("Unexpected EOF, available $availableBytes bytes, requested: $bytesCount")
-        }
-        val array = ByteArray(bytesCount)
-        read(array, 0, bytesCount)
-        return array
-    }
-
-    fun nextFloat(): Float {
-        skipOverTags()
-        val res = when (curByte) {
-            NEXT_FLOAT -> Float.fromBits(readInt())
-            NEXT_HALF -> floatFromHalfBits(readShort())
-            else -> throw CborDecodingException("float header", curByte)
-        }
-        readByte()
-        return res
-    }
-
-    fun nextDouble(): Double {
-        skipOverTags()
-        val res = when (curByte) {
-            NEXT_DOUBLE -> Double.fromBits(readLong())
-            NEXT_FLOAT -> Float.fromBits(readInt()).toDouble()
-            NEXT_HALF -> floatFromHalfBits(readShort()).toDouble()
-            else -> throw CborDecodingException("double header", curByte)
-        }
-        readByte()
-        return res
-    }
-
-    private fun readLong(): Long {
-        var result = 0L
-        for (i in 0..7) {
-            val byte = input.read()
-            result = (result shl 8) or byte.toLong()
-        }
-        return result
-    }
-
-    private fun readShort(): Short {
-        val highByte = input.read()
-        val lowByte = input.read()
-        return (highByte shl 8 or lowByte).toShort()
-    }
-
-    private fun readInt(): Int {
-        var result = 0
-        for (i in 0..3) {
-            val byte = input.read()
-            result = (result shl 8) or byte
-        }
-        return result
-    }
-
-    /**
-     * Skips the current value element. Bytes are processed to determine the element type (and corresponding length), to
-     * determine how many bytes to skip.
-     *
-     * For primitive (finite length) elements (e.g. unsigned integer, text string), their length is read and
-     * corresponding number of bytes are skipped.
-     *
-     * For elements that contain children (e.g. array, map), the child count is read and added to a "length stack"
-     * (which represents the "number of elements" at each depth of the CBOR data structure). When a child element has
-     * 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() {
-        val lengthStack = mutableListOf<Int>()
-
-        skipOverTags()
-
-        do {
-            if (isEof()) throw CborDecodingException("Unexpected EOF while skipping element")
-
-            if (isIndefinite()) {
-                lengthStack.add(LENGTH_STACK_INDEFINITE)
-            } else if (isEnd()) {
-                if (lengthStack.removeLastOrNull() != LENGTH_STACK_INDEFINITE)
-                    throw CborDecodingException("next data item", curByte)
-                prune(lengthStack)
-            } else {
-                val header = curByte and 0b111_00000
-                val length = elementLength()
-                if (header == HEADER_ARRAY || header == HEADER_MAP) {
-                    if (length > 0) lengthStack.add(length)
-                    skipOverTags()
-                } else {
-                    input.skip(length)
-                    prune(lengthStack)
-                }
-            }
-
-            readByte()
-        } while (lengthStack.isNotEmpty())
-    }
-
-    /**
-     * Removes an item from the top of the [lengthStack], cascading the removal if the item represents the last item
-     * (i.e. a length value of `1`) at its stack depth.
-     *
-     * For example, pruning a [lengthStack] of `[3, 2, 1, 1]` would result in `[3, 1]`.
-     */
-    private fun prune(lengthStack: MutableList<Int>) {
-        for (i in lengthStack.lastIndex downTo 0) {
-            when (lengthStack[i]) {
-                LENGTH_STACK_INDEFINITE -> break
-                1 -> lengthStack.removeAt(i)
-                else -> {
-                    lengthStack[i] = lengthStack[i] - 1
-                    break
-                }
-            }
-        }
-    }
-
-    /**
-     * Determines if [curByte] represents an indefinite length CBOR item.
-     *
-     * Per [RFC 7049: 2.2. Indefinite Lengths for Some Major Types](https://tools.ietf.org/html/rfc7049#section-2.2):
-     * > Four CBOR items (arrays, maps, byte strings, and text strings) can be encoded with an indefinite length
-     */
-    private fun isIndefinite(): Boolean {
-        val majorType = curByte and 0b111_00000
-        val value = curByte and 0b000_11111
-
-        return value == ADDITIONAL_INFORMATION_INDEFINITE_LENGTH &&
-            (majorType == HEADER_ARRAY || majorType == HEADER_MAP ||
-                majorType == HEADER_BYTE_STRING.toInt() || majorType == HEADER_STRING.toInt())
-    }
-
-    /**
-     * Determines the length of the CBOR item represented by [curByte]; length has specific meaning based on the type:
-     *
-     * | Major type          | Length represents number of... |
-     * |---------------------|--------------------------------|
-     * | 0. unsigned integer | bytes                          |
-     * | 1. negative integer | bytes                          |
-     * | 2. byte string      | bytes                          |
-     * | 3. string           | bytes                          |
-     * | 4. array            | data items (values)            |
-     * | 5. map              | sub-items (keys + values)      |
-     * | 6. tag              | bytes                          |
-     */
-    private fun elementLength(): Int {
-        val majorType = curByte and 0b111_00000
-        val additionalInformation = curByte and 0b000_11111
-
-        return when (majorType) {
-            HEADER_BYTE_STRING.toInt(), HEADER_STRING.toInt(), HEADER_ARRAY -> readNumber().toInt()
-            HEADER_MAP -> readNumber().toInt() * 2
-            else -> when (additionalInformation) {
-                24 -> 1
-                25 -> 2
-                26 -> 4
-                27 -> 8
-                else -> 0
-            }
-        }
-    }
+internal const val SINGLE_PRECISION_NORMALIZE_BASE = 0.5f
 
-    /**
-     * Indefinite-length byte sequences contain an unknown number of fixed-length byte sequences (chunks).
-     *
-     * @return [ByteArray] containing all of the concatenated bytes found in the buffer.
-     */
-    private fun readIndefiniteLengthBytes(): ByteArray {
-        val byteStrings = mutableListOf<ByteArray>()
-        do {
-            byteStrings.add(readBytes())
-            readByte()
-        } while (!isEnd())
-        return byteStrings.flatten()
-    }
-}
 
 @OptIn(ExperimentalSerializationApi::class)
-private fun SerialDescriptor.getElementIndexOrThrow(name: String): Int {
-    val index = getElementIndex(name)
-    if (index == CompositeDecoder.UNKNOWN_NAME)
-        throw SerializationException("$serialName does not contain element with name '$name." +
-            " You can enable 'CborBuilder.ignoreUnknownKeys' property to ignore unknown keys")
-    return index
-}
-
-private fun Iterable<ByteArray>.flatten(): ByteArray {
-    val output = ByteArray(sumOf { it.size })
-    var position = 0
-    for (chunk in this) {
-        chunk.copyInto(output, position)
-        position += chunk.size
-    }
-
-    return output
-}
-
-@OptIn(ExperimentalSerializationApi::class)
-private fun SerialDescriptor.isByteString(index: Int): Boolean {
+internal fun SerialDescriptor.isByteString(index: Int): Boolean {
     return getElementAnnotations(index).find { it is ByteString } != null
 }
 
-private fun SerialDescriptor.isInlineByteString(): Boolean {
+
+internal fun SerialDescriptor.isInlineByteString(): Boolean {
     // inline item classes should only have 1 item
     return isInline && isByteString(0)
 }
 
+@OptIn(ExperimentalSerializationApi::class)
+internal fun SerialDescriptor.getValueTags(index: Int): ULongArray? = findAnnotation<ValueTags>(index)?.tags
 
-private val normalizeBaseBits = SINGLE_PRECISION_NORMALIZE_BASE.toBits()
+@OptIn(ExperimentalSerializationApi::class)
+internal fun SerialDescriptor.getKeyTags(index: Int): ULongArray? = findAnnotation<KeyTags>(index)?.tags
 
+@OptIn(ExperimentalSerializationApi::class)
+internal fun SerialDescriptor.getCborLabel(index: Int): Long? = findAnnotation<CborLabel>(index)?.label
 
-/*
- * For details about half-precision floating-point numbers see https://tools.ietf.org/html/rfc7049#appendix-D
- */
-private fun floatFromHalfBits(bits: Short): Float {
-    val intBits = bits.toInt()
-
-    val negative = (intBits and 0x8000) != 0
-    val halfExp = intBits shr 10 and HALF_PRECISION_MAX_EXPONENT
-    val halfMant = intBits and HALF_PRECISION_MAX_MANTISSA
-
-    val exp: Int
-    val mant: Int
-
-    when (halfExp) {
-        HALF_PRECISION_MAX_EXPONENT -> {
-            // if exponent maximal - value is NaN or Infinity
-            exp = SINGLE_PRECISION_MAX_EXPONENT
-            mant = halfMant
-        }
-        0 -> {
-            if (halfMant == 0) {
-                // if exponent and mantissa are zero - value is zero
-                mant = 0
-                exp = 0
-            } else {
-                // if exponent is zero and mantissa non-zero - value denormalized. normalize it
-                var res = Float.fromBits(normalizeBaseBits + halfMant)
-                res -= SINGLE_PRECISION_NORMALIZE_BASE
-                return if (negative) -res else res
-            }
-        }
-        else -> {
-            // normalized value
-            exp = (halfExp + (SINGLE_PRECISION_EXPONENT_BIAS - HALF_PRECISION_EXPONENT_BIAS))
-            mant = halfMant
-        }
-    }
-
-    val res = Float.fromBits((exp shl 23) or (mant shl 13))
-    return if (negative) -res else res
+@OptIn(ExperimentalSerializationApi::class)
+internal fun SerialDescriptor.hasArrayTag(): Boolean {
+    return annotations.any { it is CborArray }
 }
+
+@OptIn(ExperimentalSerializationApi::class)
+internal inline fun <reified A : Annotation> SerialDescriptor.findAnnotation(elementIndex: Int): A? =
+    getElementAnnotations(elementIndex).firstOrNull { it is A } as A?
+
+
+@OptIn(ExperimentalSerializationApi::class)
+internal fun SerialDescriptor.getObjectTags(): ULongArray? {
+    return annotations.filterIsInstance<ObjectTags>().firstOrNull()?.tags
+}
\ No newline at end of file
diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Streams.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Streams.kt
index 0e5b477..fdbfca6 100644
--- a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Streams.kt
+++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Streams.kt
@@ -4,8 +4,6 @@
 
 package kotlinx.serialization.cbor.internal
 
-import kotlinx.serialization.*
-
 internal class ByteArrayInput(private var array: ByteArray) {
     private var position: Int = 0
     public val availableBytes: Int get() = array.size - position
@@ -59,6 +57,10 @@
         return newArray
     }
 
+    fun copyFrom(src: ByteArrayOutput) {
+        write(src.array, count = src.position)
+    }
+
     fun write(buffer: ByteArray, offset: Int = 0, count: Int = buffer.size) {
         // avoid int overflow
         if (offset < 0 || offset > buffer.size || count < 0
diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/SuppressAnimalSniffer.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/SuppressAnimalSniffer.kt
new file mode 100644
index 0000000..038b821
--- /dev/null
+++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/SuppressAnimalSniffer.kt
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.cbor.internal
+
+/**
+ * Suppresses Animal Sniffer plugin errors for certain methods.
+ * Such methods include references to Java 8 methods that are not
+ * available in Android API, but can be desugared by R8.
+ */
+@Retention(AnnotationRetention.BINARY)
+@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
+internal annotation class SuppressAnimalSniffer
diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborArrayTest.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborArrayTest.kt
new file mode 100644
index 0000000..c0b8595
--- /dev/null
+++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborArrayTest.kt
@@ -0,0 +1,179 @@
+package kotlinx.serialization.cbor
+
+import kotlinx.serialization.*
+import kotlin.test.*
+
+
+class CborArrayTest {
+
+    @Test
+    fun writeReadVerifyArraySize1() {
+        /**
+         * 81    # array(1)
+         *    26 # negative(6)
+         */
+        val referenceHexString = "8126"
+        val reference = ClassAs1Array(alg = -7)
+
+        val cbor = Cbor.CoseCompliant
+        assertEquals(referenceHexString, cbor.encodeToHexString(ClassAs1Array.serializer(), reference))
+        assertEquals(reference, cbor.decodeFromHexString(ClassAs1Array.serializer(), referenceHexString))
+    }
+
+    @Test
+    fun writeReadVerifyArraySize2() {
+        /**
+         * C8              # tag(8)
+         *    82           # array(2)
+         *       26        # negative(6)
+         *       63        # text(3)
+         *          666F6F # "foo"
+         */
+        val referenceHexString = "c8822663666f6f"
+        val reference = ClassAs2Array(alg = -7, kid = "foo")
+
+        val cbor = Cbor.CoseCompliant
+        assertEquals(referenceHexString, cbor.encodeToHexString(ClassAs2Array.serializer(), reference))
+        assertEquals(reference, cbor.decodeFromHexString(ClassAs2Array.serializer(), referenceHexString))
+    }
+
+    @Test
+    fun writeReadVerifyArraySize4Nullable() {
+        /**
+         * 84           # array(4)
+         *    26        # negative(6)
+         *    63        # text(3)
+         *       626172 # "bar"
+         *    F6        # primitive(22)
+         *    A0        # map(0)
+         */
+        val referenceHexString = "842663626172f6a0"
+        val reference = ClassAs4ArrayNullable(alg = -7, kid = "bar", iv = null, array = null)
+
+        val cbor = Cbor.CoseCompliant
+
+        assertEquals(referenceHexString, cbor.encodeToHexString(ClassAs4ArrayNullable.serializer(), reference))
+        assertEquals(reference, cbor.decodeFromHexString(ClassAs4ArrayNullable.serializer(), referenceHexString))
+    }
+
+    @Test
+    fun writeReadVerifyClassWithArray() {
+        /**
+         * A1                 # map(1)
+         *    65              # text(5)
+         *       6172726179   # "array"
+         *    C8              # tag(8)
+         *       82           # array(2)
+         *          26        # negative(6)
+         *          63        # text(3)
+         *             626172 # "bar"
+         */
+        val referenceHexString = "a1656172726179c8822663626172"
+        val reference = ClassWithArray(array = ClassAs2Array(alg = -7, kid = "bar"))
+
+        val cbor = Cbor.CoseCompliant
+        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"))
+            )
+        )
+    }
+
+
+    @Test
+    fun writeReadVerifyDoubleTaggedClassWithArray() {
+        /**
+         * A1                    # map(1)
+         *    65                 # text(5)
+         *       6172726179      # "array"
+         *    C9                 # tag(9)
+         *       C8              # tag(8)
+         *          82           # array(2)
+         *             26        # negative(6)
+         *             63        # text(3)
+         *                626172 # "bar"
+         */
+        val referenceHexString = "a1656172726179c9c8822663626172"
+        val reference = DoubleTaggedClassWithArray(array = ClassAs2Array(alg = -7, kid = "bar"))
+
+        val cbor = Cbor.CoseCompliant
+        assertEquals(referenceHexString, cbor.encodeToHexString(DoubleTaggedClassWithArray.serializer(), reference))
+        assertEquals(reference, cbor.decodeFromHexString(DoubleTaggedClassWithArray.serializer(), referenceHexString))
+    }
+
+    @CborArray
+    @Serializable
+    data class ClassAs1Array(
+        @SerialName("alg")
+        val alg: Int,
+    )
+
+    @CborArray
+    @ObjectTags(8U)
+    @Serializable
+    data class ClassAs2Array(
+        @SerialName("alg")
+        val alg: Int,
+        @SerialName("kid")
+        val kid: String,
+    )
+
+    @CborArray
+    @Serializable
+    data class ClassAs4ArrayNullable(
+        @SerialName("alg")
+        val alg: Int,
+        @SerialName("kid")
+        val kid: String,
+        @SerialName("iv")
+        @ByteString
+        val iv: ByteArray?,
+        @SerialName("array")
+        val array: ClassWithArray?
+    ) {
+        override fun equals(other: Any?): Boolean {
+            if (this === other) return true
+            if (other == null || this::class != other::class) return false
+
+            other as ClassAs4ArrayNullable
+
+            if (alg != other.alg) return false
+            if (kid != other.kid) return false
+            if (iv != null) {
+                if (other.iv == null) return false
+                if (!iv.contentEquals(other.iv)) return false
+            } else if (other.iv != null) return false
+            if (array != other.array) return false
+
+            return true
+        }
+
+        override fun hashCode(): Int {
+            var result = alg
+            result = 31 * result + kid.hashCode()
+            result = 31 * result + (iv?.contentHashCode() ?: 0)
+            result = 31 * result + (array?.hashCode() ?: 0)
+            return result
+        }
+    }
+
+
+    @Serializable
+    data class ClassWithArray(
+        @SerialName("array")
+        val array: ClassAs2Array,
+    )
+
+
+    @Serializable
+    data class DoubleTaggedClassWithArray(
+        @ValueTags(9u)
+        @SerialName("array")
+        val array: ClassAs2Array,
+    )
+}
+
diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborDecoderTest.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborDecoderTest.kt
new file mode 100644
index 0000000..92aee67
--- /dev/null
+++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborDecoderTest.kt
@@ -0,0 +1,377 @@
+/*
+ * Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:OptIn(ExperimentalUnsignedTypes::class)
+
+package kotlinx.serialization.cbor
+
+import kotlinx.serialization.*
+import kotlinx.serialization.SimpleSealed.*
+import kotlinx.serialization.cbor.internal.*
+import kotlin.test.*
+
+class CborDecoderTest {
+
+    private val ignoreUnknownKeys = Cbor { ignoreUnknownKeys = true }
+
+    @Test
+    fun testDecodeSimpleObject() {
+        assertEquals(Simple("str"), Cbor.decodeFromHexString(Simple.serializer(), "bf616163737472ff"))
+    }
+
+    @Test
+    fun testDecodeComplicatedObject() {
+        val test = TypesUmbrella(
+            "Hello, world!",
+            42,
+            null,
+            listOf("a", "b"),
+            mapOf(1 to true, 2 to false),
+            Simple("lol"),
+            listOf(Simple("kek")),
+            HexConverter.parseHexBinary("cafe"),
+            HexConverter.parseHexBinary("cafe")
+        )
+        // with maps, lists & strings of indefinite length
+        assertEquals(
+            test, Cbor.decodeFromHexString(
+                TypesUmbrella.serializer(),
+                "bf637374726d48656c6c6f2c20776f726c64216169182a686e756c6c61626c65f6646c6973749f61616162ff636d6170bf01f502f4ff65696e6e6572bf6161636c6f6cff6a696e6e6572734c6973749fbf6161636b656bffff6a62797465537472696e675f42cafeff696279746541727261799f383521ffff"
+            )
+        )
+        // with maps, lists & strings of definite length
+        assertEquals(
+            test, Cbor.decodeFromHexString(
+                TypesUmbrella.serializer(),
+                "a9646c6973748261616162686e756c6c61626c65f6636d6170a202f401f56169182a6a696e6e6572734c69737481a16161636b656b637374726d48656c6c6f2c20776f726c642165696e6e6572a16161636c6f6c6a62797465537472696e6742cafe6962797465417272617982383521"
+            )
+        )
+    }
+
+    @Test
+    fun testReadByteStringWhenNullable() {
+        /* A1                         # map(1)
+         *    6A                      # text(10)
+         *       62797465537472696E67 # "byteString"
+         *    44                      # bytes(4)
+         *       01020304             # "\x01\x02\x03\x04"
+         */
+        assertEquals(
+            expected = NullableByteString(byteArrayOf(1, 2, 3, 4)),
+            actual = Cbor.decodeFromHexString(
+                deserializer = NullableByteString.serializer(),
+                hex = "a16a62797465537472696e674401020304"
+            )
+        )
+
+        /* A1                         # map(1)
+         *    6A                      # text(10)
+         *       62797465537472696E67 # "byteString"
+         *    F6                      # primitive(22)
+         */
+        assertEquals(
+            expected = NullableByteString(byteString = null),
+            actual = Cbor.decodeFromHexString(
+                deserializer = NullableByteString.serializer(),
+                hex = "a16a62797465537472696e67f6"
+            )
+        )
+    }
+
+    @Test
+    fun testNullables() {
+        Cbor.decodeFromHexString<NullableByteStringDefaultNull>("a0")
+    }
+
+    /**
+     * CBOR hex data represents serialized versions of [TypesUmbrella] (which does **not** have a root property 'a') so
+     * decoding to [Simple] (which has the field 'a') is expected to fail.
+     */
+    @Test
+    fun testIgnoreUnknownKeysFailsWhenCborDataIsMissingKeysThatArePresentInKotlinClass() {
+        // with maps & lists of indefinite length
+        assertFailsWithMessage<SerializationException>("Field 'a' is required") {
+            ignoreUnknownKeys.decodeFromHexString(
+                Simple.serializer(),
+                "bf637374726d48656c6c6f2c20776f726c64216169182a686e756c6c61626c65f6646c6973749f61616162ff636d6170bf01f502f4ff65696e6e6572bf6161636c6f6cff6a696e6e6572734c6973749fbf6161636b656bffffff"
+            )
+        }
+
+        // with maps & lists of definite length
+        assertFailsWithMessage<SerializationException>("Field 'a' is required") {
+            ignoreUnknownKeys.decodeFromHexString(
+                Simple.serializer(),
+                "a7646c6973748261616162686e756c6c61626c65f6636d6170a202f401f56169182a6a696e6e6572734c69737481a16161636b656b637374726d48656c6c6f2c20776f726c642165696e6e6572a16161636c6f6c"
+            )
+        }
+    }
+
+    @Test
+    fun testIgnoreUnknownKeysFailsWhenDecodingIncompleteCbor() {
+        /* A3                 # map(3)
+         *    63              # text(3)
+         *       737472       # "str"
+         *    66              # text(6)
+         *       737472696E67 # "string"
+         *    61              # text(1)
+         *       69           # "i"
+         *    00              # unsigned(0)
+         *    66              # text(6)
+         *       69676E6F7265 # "ignore"
+         * (missing value associated with "ignore" key)
+         */
+        assertFailsWithMessage<CborDecodingException>("Unexpected EOF while skipping element") {
+            ignoreUnknownKeys.decodeFromHexString(
+                TypesUmbrella.serializer(),
+                "a36373747266737472696e676169006669676e6f7265"
+            )
+        }
+
+        /* A3                 # map(3)
+         *    63              # text(3)
+         *       737472       # "str"
+         *    66              # text(6)
+         *       737472696E67 # "string"
+         *    61              # text(1)
+         *       69           # "i"
+         *    00              # unsigned(0)
+         *    66              # text(6)
+         *       69676E6F7265 # "ignore"
+         *    A2              # map(2)
+         * (missing map contents associated with "ignore" key)
+         */
+        assertFailsWithMessage<CborDecodingException>("Unexpected EOF while skipping element") {
+            ignoreUnknownKeys.decodeFromHexString(
+                TypesUmbrella.serializer(),
+                "a36373747266737472696e676169006669676e6f7265a2"
+            )
+        }
+    }
+
+    @Test
+    fun testIgnoreUnknownKeysFailsWhenEncounteringPreemptiveBreak() {
+        /* A3                 # map(3)
+         *    63              # text(3)
+         *       737472       # "str"
+         *    66              # text(6)
+         *       737472696E67 # "string"
+         *    66              # text(6)
+         *       69676E6F7265 # "ignore"
+         *    FF              # primitive(*)
+         */
+        assertFailsWithMessage<CborDecodingException>("Expected next data item, but found FF") {
+            ignoreUnknownKeys.decodeFromHexString(
+                TypesUmbrella.serializer(),
+                "a36373747266737472696e676669676e6f7265ff"
+            )
+        }
+    }
+
+
+    @Test
+    fun testDecodeCborWithUnknownField() {
+        assertEquals(
+            expected = Simple("123"),
+            actual = ignoreUnknownKeys.decodeFromHexString(
+                deserializer = Simple.serializer(),
+
+                /* BF           # map(*)
+                 *    61        # text(1)
+                 *       61     # "a"
+                 *    63        # text(3)
+                 *       313233 # "123"
+                 *    61        # text(1)
+                 *       62     # "b"
+                 *    63        # text(3)
+                 *       393837 # "987"
+                 *    FF        # primitive(*)
+                 */
+                hex = "bf616163313233616263393837ff"
+            )
+        )
+    }
+
+    @Test
+    fun testDecodeCborWithUnknownNestedIndefiniteFields() {
+        assertEquals(
+            expected = Simple("123"),
+            actual = ignoreUnknownKeys.decodeFromHexString(
+                deserializer = Simple.serializer(),
+
+                /* BF             # map(*)
+                 *    61          # text(1)
+                 *       61       # "a"
+                 *    63          # text(3)
+                 *       313233   # "123"
+                 *    61          # text(1)
+                 *       62       # "b"
+                 *    BF          # map(*)
+                 *       7F       # text(*)
+                 *          61    # text(1)
+                 *             78 # "x"
+                 *          FF    # primitive(*)
+                 *       A1       # map(1)
+                 *          61    # text(1)
+                 *             79 # "y"
+                 *          0A    # unsigned(10)
+                 *       FF       # primitive(*)
+                 *    61          # text(1)
+                 *       63       # "c"
+                 *    9F          # array(*)
+                 *       01       # unsigned(1)
+                 *       02       # unsigned(2)
+                 *       03       # unsigned(3)
+                 *       FF       # primitive(*)
+                 *    FF          # primitive(*)
+                 */
+                hex = "bf6161633132336162bf7f6178ffa161790aff61639f010203ffff"
+            )
+        )
+    }
+
+    /**
+     * The following CBOR diagnostic output demonstrates the additional fields (prefixed with `+` in front of each line)
+     * present in the encoded CBOR data that does not have associated fields in the Kotlin classes (they will be skipped
+     * over with `ignoreUnknownKeys` is enabled).
+     *
+     * ```diff
+     *   {
+     * +   "extra": [
+     * +     9,
+     * +     8,
+     * +     7
+     * +   ],
+     *     "boxed": [
+     *       [
+     *         "kotlinx.serialization.SimpleSealed.SubSealedA",
+     *         {
+     *           "s": "a",
+     * +         "newA": {
+     * +           "x": 1,
+     * +           "y": 2
+     * +         }
+     *         }
+     *       ],
+     *       [
+     *         "kotlinx.serialization.SimpleSealed.SubSealedB",
+     *         {
+     *           "i": 1
+     *         }
+     *       ]
+     *     ]
+     *   }
+     * ```
+     */
+    @Test
+    fun testDecodeCborWithUnknownKeysInSealedClasses() {
+        /* BF                      # map(*)
+         *    65                   # text(5)
+         *       6578747261        # "extra"
+         *    83                   # array(3)
+         *       09                # unsigned(9)
+         *       08                # unsigned(8)
+         *       07                # unsigned(7)
+         *    65                   # text(5)
+         *       626F786564        # "boxed"
+         *    9F                   # array(*)
+         *       9F                # array(*)
+         *          78 2D          # text(45)
+         *             6B6F746C696E782E73657269616C697A6174696F6E2E53696D706C655365616C65642E5375625365616C656441 # "kotlinx.serialization.SimpleSealed.SubSealedA"
+         *          BF             # map(*)
+         *             61          # text(1)
+         *                73       # "s"
+         *             61          # text(1)
+         *                61       # "a"
+         *             64          # text(4)
+         *                6E657741 # "newA"
+         *             BF          # map(*)
+         *                61       # text(1)
+         *                   78    # "x"
+         *                01       # unsigned(1)
+         *                61       # text(1)
+         *                   79    # "y"
+         *                02       # unsigned(2)
+         *                FF       # primitive(*)
+         *             FF          # primitive(*)
+         *          FF             # primitive(*)
+         *       9F                # array(*)
+         *          78 2D          # text(45)
+         *             6B6F746C696E782E73657269616C697A6174696F6E2E53696D706C655365616C65642E5375625365616C656442 # "kotlinx.serialization.SimpleSealed.SubSealedB"
+         *          BF             # map(*)
+         *             61          # text(1)
+         *                69       # "i"
+         *             01          # unsigned(1)
+         *             FF          # primitive(*)
+         *          FF             # primitive(*)
+         *       FF                # primitive(*)
+         *    FF                   # primitive(*)
+         */
+
+        assertEquals(
+            expected = SealedBox(
+                listOf(
+                    SubSealedA("a"),
+                    SubSealedB(1)
+                )
+            ),
+            actual = ignoreUnknownKeys.decodeFromHexString(
+                SealedBox.serializer(),
+                "bf6565787472618309080765626f7865649f9f782d6b6f746c696e782e73657269616c697a6174696f6e2e53696d706c655365616c65642e5375625365616c656441bf61736161646e657741bf617801617902ffffff9f782d6b6f746c696e782e73657269616c697a6174696f6e2e53696d706c655365616c65642e5375625365616c656442bf616901ffffffff"
+            )
+        )
+    }
+
+    @Test
+    fun testReadCustomByteString() {
+        assertEquals(
+            expected = TypeWithCustomByteString(CustomByteString(0x11, 0x22, 0x33)),
+            actual = Cbor.decodeFromHexString("bf617843112233ff")
+        )
+    }
+
+    @Test
+    fun testReadNullableCustomByteString() {
+        assertEquals(
+            expected = TypeWithNullableCustomByteString(CustomByteString(0x11, 0x22, 0x33)),
+            actual = Cbor.decodeFromHexString("bf617843112233ff")
+        )
+    }
+
+    @Test
+    fun testReadNullCustomByteString() {
+        assertEquals(
+            expected = TypeWithNullableCustomByteString(null),
+            actual = Cbor.decodeFromHexString("bf6178f6ff")
+        )
+    }
+
+    @Test
+    fun testReadValueClassWithByteString() {
+        assertContentEquals(
+            expected = byteArrayOf(0x11, 0x22, 0x33),
+            actual = Cbor.decodeFromHexString<ValueClassWithByteString>("43112233").x
+        )
+    }
+
+    @Test
+    fun testReadValueClassCustomByteString() {
+        assertEquals(
+            expected = ValueClassWithCustomByteString(CustomByteString(0x11, 0x22, 0x33)),
+            actual = Cbor.decodeFromHexString("43112233")
+        )
+    }
+
+    @Test
+    fun testReadValueClassWithUnlabeledByteString() {
+        assertContentEquals(
+            expected = byteArrayOf(
+                0x11,
+                0x22,
+                0x33
+            ),
+            actual = Cbor.decodeFromHexString<ValueClassWithUnlabeledByteString>("43112233").x.x
+        )
+    }
+
+}
diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborDefiniteLengthTest.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborDefiniteLengthTest.kt
new file mode 100644
index 0000000..b19a409
--- /dev/null
+++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborDefiniteLengthTest.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2017-2023 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 kotlin.test.*
+
+
+class CborDefiniteLengthTest {
+    @Test
+    fun writeComplicatedClass() {
+        val test = TypesUmbrella(
+            "Hello, world!",
+            42,
+            null,
+            listOf("a", "b"),
+            mapOf(1 to true, 2 to false),
+            Simple("lol"),
+            listOf(Simple("kek")),
+            HexConverter.parseHexBinary("cafe"),
+            HexConverter.parseHexBinary("cafe")
+        )
+        assertEquals(
+            "a9637374726d48656c6c6f2c20776f726c64216169182a686e756c6c61626c65f6646c6973748261616162636d6170a201f502f465696e6e6572a16161636c6f6c6a696e6e6572734c69737481a16161636b656b6a62797465537472696e6742cafe6962797465417272617982383521",
+            Cbor { useDefiniteLengthEncoding = true }.encodeToHexString(TypesUmbrella.serializer(), test)
+        )
+    }
+
+}
\ No newline at end of file
diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborIsoTest.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborIsoTest.kt
new file mode 100644
index 0000000..49ef339
--- /dev/null
+++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborIsoTest.kt
@@ -0,0 +1,50 @@
+package kotlinx.serialization.cbor
+
+import kotlinx.serialization.*
+import kotlin.test.*
+
+class CborIsoTest {
+
+    private val reference = DataClass(
+        bytes = "foo".encodeToByteArray()
+    )
+
+    /**
+     * A1               # map(1)
+     *    65            # text(5)
+     *       6279746573 # "bytes"
+     *    43            # bytes(3)
+     *       666F6F     # "foo"
+     *
+     */
+    private val referenceHexString = "a165627974657343666f6f"
+
+    @Test
+    fun writeReadVerifyCoseSigned() {
+        val cbor = Cbor {
+            alwaysUseByteString = true
+            useDefiniteLengthEncoding = true
+        }
+        assertEquals(reference, cbor.decodeFromHexString(DataClass.serializer(), referenceHexString))
+        assertEquals(referenceHexString, cbor.encodeToHexString(DataClass.serializer(), reference))
+    }
+
+    @Serializable
+    data class DataClass(
+        @SerialName("bytes")
+        val bytes: ByteArray,
+    ) {
+        override fun equals(other: Any?): Boolean {
+            if (this === other) return true
+            if (other == null || this::class != other::class) return false
+
+            other as DataClass
+
+            return bytes.contentEquals(other.bytes)
+        }
+
+        override fun hashCode(): Int {
+            return bytes.contentHashCode()
+        }
+    }
+}
\ No newline at end of file
diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborLabelTest.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborLabelTest.kt
new file mode 100644
index 0000000..0ecb528
--- /dev/null
+++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborLabelTest.kt
@@ -0,0 +1,155 @@
+package kotlinx.serialization.cbor
+
+import kotlinx.serialization.*
+import kotlinx.serialization.cbor.internal.CborDecodingException
+import kotlin.test.*
+
+
+class CborLabelTest {
+
+    private val reference = ClassWithCborLabel(alg = -7)
+
+
+    /**
+     * BF    # map(*)
+     *    01 # unsigned(1)
+     *    26 # negative(6)
+     *    FF # primitive(*)
+     */
+    private val referenceHexLabelString = "bf0126ff"
+
+    /**
+     * BF           # map(*)
+     *    63        # text(3)
+     *       616C67 # "alg"
+     *    26        # negative(6)
+     *    FF        # primitive(*)
+     */
+    private val referenceHexNameString = "bf63616c6726ff"
+
+
+    @Test
+    fun writeReadVerifyCborLabel() {
+        val cbor = Cbor {
+            preferCborLabelsOverNames = true
+        }
+        assertEquals(referenceHexLabelString, cbor.encodeToHexString(ClassWithCborLabel.serializer(), reference))
+        assertEquals(reference, cbor.decodeFromHexString(ClassWithCborLabel.serializer(), referenceHexLabelString))
+    }
+
+    @Test
+    fun writeReadVerifySerialName() {
+        val cbor = Cbor {
+            preferCborLabelsOverNames = false
+        }
+        assertEquals(referenceHexNameString, cbor.encodeToHexString(ClassWithCborLabel.serializer(), reference))
+        assertEquals(reference, cbor.decodeFromHexString(ClassWithCborLabel.serializer(), referenceHexNameString))
+    }
+
+    @Test
+    fun writeReadVerifyCborLabelWithTags() {
+        val referenceWithTag = ClassWithCborLabelAndTag(alg = -7)
+        /**
+         * A1       # map(1)
+         *    C5    # tag(5)
+         *       01 # unsigned(1)
+         *    26    # negative(6)
+         */
+        val referenceHexLabelWithTagString = "a1c50126"
+        val cbor = Cbor {
+            preferCborLabelsOverNames = true
+            encodeKeyTags = true
+            verifyKeyTags = true
+            useDefiniteLengthEncoding = true
+        }
+        assertEquals(referenceHexLabelWithTagString, cbor.encodeToHexString(ClassWithCborLabelAndTag.serializer(), referenceWithTag))
+        assertEquals(referenceWithTag, cbor.decodeFromHexString(ClassWithCborLabelAndTag.serializer(), referenceHexLabelWithTagString))
+    }
+
+    @Test
+    fun writeReadVerifyCborLabelWithTagsThrowing() {
+        /**
+         * A1       # map(1)
+         *    C6    # tag(6)        // wrong tag: declared is 5U, meaning C5 in hex
+         *       01 # unsigned(1)
+         *    26    # negative(6)
+         */
+        val referenceHexLabelWithTagString = "a1c60126"
+        val cbor = Cbor {
+            preferCborLabelsOverNames = true
+            encodeKeyTags = true
+            verifyKeyTags = true
+            useDefiniteLengthEncoding = true
+        }
+        assertFailsWith(CborDecodingException::class) {
+            cbor.decodeFromHexString(ClassWithCborLabelAndTag.serializer(), referenceHexLabelWithTagString)
+        }
+    }
+
+    @Test
+    fun writeReadVerifyCborLabelWithTagsAndUnknownKeys() {
+        val referenceWithTag = ClassWithCborLabelAndTag(alg = -7)
+        /**
+         * A2           # map(2)
+         *    C5        # tag(5)
+         *       01     # unsigned(1)
+         *    26        # negative(6)
+         *    02        # unsigned(2)
+         *    63        # text(3)
+         *       62617A # "baz"
+         */
+        val referenceHexLabelWithTagString = "a2c50126026362617a"
+        val cbor = Cbor {
+            preferCborLabelsOverNames = true
+            encodeKeyTags = true
+            verifyKeyTags = true
+            ignoreUnknownKeys = true
+            useDefiniteLengthEncoding = true
+        }
+        assertEquals(referenceWithTag, cbor.decodeFromHexString(ClassWithCborLabelAndTag.serializer(), referenceHexLabelWithTagString))
+    }
+
+    @Test
+    fun writeClassWithoutLabelBuPreferLabel() {
+
+        //only serialName is present, no label, so fallback to serialName
+        val referenceWithoutLabel = ClassWithoutCborLabel(algorithm = 9)
+        /**
+         * BF           # map(*)
+         *    63        # text(3)
+         *       616C67 # "alg"
+         *    09        # unsigned(9)
+         *    FF        # primitive(*)
+         */
+
+        val referenceHexStringWithoutLabel = "bf63616c6709ff"
+        val cbor = Cbor {
+            preferCborLabelsOverNames = true
+        }
+
+        assertEquals(referenceWithoutLabel, cbor.decodeFromHexString(ClassWithoutCborLabel.serializer(), referenceHexStringWithoutLabel))
+    }
+
+    @Serializable
+    data class ClassWithCborLabel(
+        @CborLabel(1)
+        @SerialName("alg")
+        val alg: Int
+    )
+
+    @Serializable
+    data class ClassWithCborLabelAndTag(
+        @CborLabel(1)
+        @SerialName("alg")
+        @KeyTags(5U)
+        val alg: Int
+    )
+
+    @Serializable
+    data class ClassWithoutCborLabel(
+        @SerialName("alg")
+        val algorithm: Int
+    )
+
+}
+
diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborParserTest.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborParserTest.kt
new file mode 100644
index 0000000..a8f5af5
--- /dev/null
+++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborParserTest.kt
@@ -0,0 +1,559 @@
+/*
+ * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:OptIn(ExperimentalUnsignedTypes::class)
+
+package kotlinx.serialization.cbor
+
+import kotlinx.serialization.*
+import kotlinx.serialization.SimpleSealed.*
+import kotlinx.serialization.cbor.internal.*
+import kotlin.test.*
+
+class CborParserTest {
+
+    private fun withParser(input: String, block: CborParser.() -> Unit) {
+        val bytes = HexConverter.parseHexBinary(input.uppercase())
+        CborParser(ByteArrayInput(bytes), false).block()
+    }
+
+    @Test
+    fun testParseIntegers() {
+        withParser("0C1903E8") {
+            assertEquals(12L, nextNumber())
+            assertEquals(1000L, nextNumber())
+        }
+        withParser("203903e7") {
+            assertEquals(-1L, nextNumber())
+            assertEquals(-1000L, nextNumber())
+        }
+    }
+
+    @Test
+    fun testParseStrings() {
+        withParser("6568656C6C6F") {
+            assertEquals("hello", nextString())
+        }
+        withParser("7828737472696E672074686174206973206C6F6E676572207468616E2032332063686172616374657273") {
+            assertEquals("string that is longer than 23 characters", nextString())
+        }
+    }
+
+    @Test
+    fun testParseDoubles() {
+        withParser("fb7e37e43c8800759c") {
+            assertEquals(1e+300, nextDouble())
+        }
+        withParser("fa47c35000") {
+            assertEquals(100000.0f, nextFloat())
+        }
+    }
+
+
+    /**
+     * Test using example shown on page 11 of [RFC 7049 2.2.2](https://tools.ietf.org/html/rfc7049#section-2.2.2):
+     *
+     * ```
+     * 0b010_11111 0b010_00100 0xaabbccdd 0b010_00011 0xeeff99 0b111_11111
+     *
+     * 5F              -- Start indefinite-length byte string
+     *    44           -- Byte string of length 4
+     *       aabbccdd  -- Bytes content
+     *    43           -- Byte string of length 3
+     *       eeff99    -- Bytes content
+     *    FF           -- "break"
+     *
+     * After decoding, this results in a single byte string with seven
+     * bytes: 0xaabbccddeeff99.
+     * ```
+     */
+    @Test
+    fun testRfc7049IndefiniteByteStringExample() {
+        withParser(input = "5F44aabbccdd43eeff99FF") {
+            assertEquals(
+                expected = "aabbccddeeff99",
+                actual = HexConverter.printHexBinary(nextByteString(), lowerCase = true)
+            )
+        }
+    }
+
+
+    /**
+     * Tests skipping unknown keys associated with values of the following CBOR types:
+     * - Major type 0: an unsigned integer
+     * - Major type 1: a negative integer
+     * - Major type 2: a byte string
+     * - Major type 3: a text string
+     */
+    @Test
+    fun testSkipPrimitives() {
+        /* A4                           # map(4)
+         *    61                        # text(1)
+         *       61                     # "a"
+         *    1B FFFFFFFFFFFFFFFF       # unsigned(18446744073709551615)
+         *    61                        # text(1)
+         *       62                     # "b"
+         *    20                        # negative(0)
+         *    61                        # text(1)
+         *       63                     # "c"
+         *    42                        # bytes(2)
+         *       CAFE                   # "\xCA\xFE"
+         *    61                        # text(1)
+         *       64                     # "d"
+         *    6B                        # text(11)
+         *       48656C6C6F20776F726C64 # "Hello world"
+         */
+        withParser("a461611bffffffffffffffff616220616342cafe61646b48656c6c6f20776f726c64") {
+            expectMap(size = 4)
+            expect("a")
+            skipElement() // unsigned(18446744073709551615)
+            expect("b")
+            skipElement() // negative(0)
+            expect("c")
+            skipElement() // "\xCA\xFE"
+            expect("d")
+            skipElement() // "Hello world"
+            expectEof()
+        }
+    }
+
+    /**
+     * Tests skipping unknown keys associated with values (that are empty) of the following CBOR types:
+     * - Major type 2: a byte string
+     * - Major type 3: a text string
+     */
+    @Test
+    fun testSkipEmptyPrimitives() {
+        /* A2       # map(2)
+         *    61    # text(1)
+         *       61 # "a"
+         *    40    # bytes(0)
+         *          # ""
+         *    61    # text(1)
+         *       62 # "b"
+         *    60    # text(0)
+         *          # ""
+         */
+        withParser("a2616140616260") {
+            expectMap(size = 2)
+            expect("a")
+            skipElement() // bytes(0)
+            expect("b")
+            skipElement() // text(0)
+            expectEof()
+        }
+    }
+
+    /**
+     * Tests skipping unknown keys associated with values of the following CBOR types:
+     * - Major type 4: an array of data items
+     * - Major type 5: a map of pairs of data items
+     */
+    @Test
+    fun testSkipCollections() {
+        /* A2                                  # map(2)
+         *    61                               # text(1)
+         *       61                            # "a"
+         *    83                               # array(3)
+         *       01                            # unsigned(1)
+         *       18 FF                         # unsigned(255)
+         *       1A 00010000                   # unsigned(65536)
+         *    61                               # text(1)
+         *       62                            # "b"
+         *    A2                               # map(2)
+         *       61                            # text(1)
+         *          78                         # "x"
+         *       67                            # text(7)
+         *          6B6F746C696E78             # "kotlinx"
+         *       61                            # text(1)
+         *          79                         # "y"
+         *       6D                            # text(13)
+         *          73657269616C697A6174696F6E # "serialization"
+         */
+        withParser("a26161830118ff1a000100006162a26178676b6f746c696e7861796d73657269616c697a6174696f6e") {
+            expectMap(size = 2)
+            expect("a")
+            skipElement() // [1, 255, 65536]
+            expect("b")
+            skipElement() // {"x": "kotlinx", "y": "serialization"}
+            expectEof()
+        }
+    }
+
+    /**
+     * Tests skipping unknown keys associated with values (empty collections) of the following CBOR types:
+     * - Major type 4: an array of data items
+     * - Major type 5: a map of pairs of data items
+     */
+    @Test
+    fun testSkipEmptyCollections() {
+        /* A2       # map(2)
+         *    61    # text(1)
+         *       61 # "a"
+         *    80    # array(0)
+         *    61    # text(1)
+         *       62 # "b"
+         *    A0    # map(0)
+         */
+        withParser("a26161806162a0") {
+            expectMap(size = 2)
+            expect("a")
+            skipElement() // [1, 255, 65536]
+            expect("b")
+            skipElement() // {"x": "kotlinx", "y": "serialization"}
+            expectEof()
+        }
+    }
+
+    /**
+     * Tests skipping unknown keys associated with **indefinite length** values of the following CBOR types:
+     * - Major type 2: a byte string
+     * - Major type 3: a text string
+     * - Major type 4: an array of data items
+     * - Major type 5: a map of pairs of data items
+     */
+    @Test
+    fun testSkipIndefiniteLength() {
+        /* A4                                  # map(4)
+         *    61                               # text(1)
+         *       61                            # "a"
+         *    5F                               # bytes(*)
+         *       42                            # bytes(2)
+         *          CAFE                       # "\xCA\xFE"
+         *       43                            # bytes(3)
+         *          010203                     # "\x01\x02\x03"
+         *       FF                            # primitive(*)
+         *    61                               # text(1)
+         *       62                            # "b"
+         *    7F                               # text(*)
+         *       66                            # text(6)
+         *          48656C6C6F20               # "Hello "
+         *       65                            # text(5)
+         *          776F726C64                 # "world"
+         *       FF                            # primitive(*)
+         *    61                               # text(1)
+         *       63                            # "c"
+         *    9F                               # array(*)
+         *       67                            # text(7)
+         *          6B6F746C696E78             # "kotlinx"
+         *       6D                            # text(13)
+         *          73657269616C697A6174696F6E # "serialization"
+         *       FF                            # primitive(*)
+         *    61                               # text(1)
+         *       64                            # "d"
+         *    BF                               # map(*)
+         *       61                            # text(1)
+         *          31                         # "1"
+         *       01                            # unsigned(1)
+         *       61                            # text(1)
+         *          32                         # "2"
+         *       02                            # unsigned(2)
+         *       61                            # text(1)
+         *          33                         # "3"
+         *       03                            # unsigned(3)
+         *       FF                            # primitive(*)
+         */
+        withParser("a461615f42cafe43010203ff61627f6648656c6c6f2065776f726c64ff61639f676b6f746c696e786d73657269616c697a6174696f6eff6164bf613101613202613303ff") {
+            expectMap(size = 4)
+            expect("a")
+            skipElement() // "\xCA\xFE\x01\x02\x03"
+            expect("b")
+            skipElement() // "Hello world"
+            expect("c")
+            skipElement() // ["kotlinx", "serialization"]
+            expect("d")
+            skipElement() // {"1": 1, "2": 2, "3": 3}
+            expectEof()
+        }
+    }
+
+    /**
+     * Tests that skipping unknown keys also skips over associated tags.
+     *
+     * Includes tags on the key, tags on the value, and tags on both key and value.
+     */
+    @Test
+    fun testSkipTags() {
+        /*
+         * A4                                 # map(4)
+         * 61                              # text(1)
+         *    61                           # "a"
+         * CC                              # tag(12)
+         *    1B FFFFFFFFFFFFFFFF          # unsigned(18446744073709551615)
+         * D8 22                           # tag(34)
+         *    61                           # text(1)
+         *       62                        # "b"
+         * 20                              # negative(0)
+         * D8 38                           # tag(56)
+         *    61                           # text(1)
+         *       63                        # "c"
+         * D8 4E                           # tag(78)
+         *    42                           # bytes(2)
+         *       CAFE                      # "\xCA\xFE"
+         * 61                              # text(1)
+         *    64                           # "d"
+         * D8 5A                           # tag(90)
+         *    CC                           # tag(12)
+         *       6B                        # text(11)
+         *          48656C6C6F20776F726C64 # "Hello world"
+         */
+        withParser("A46161CC1BFFFFFFFFFFFFFFFFD822616220D8386163D84E42CAFE6164D85ACC6B48656C6C6F20776F726C64") {
+            expectMap(size = 4)
+            expect("a")
+            skipElement() // unsigned(18446744073709551615)
+            expect("b")
+            skipElement() // negative(0)
+            expect("c")
+            skipElement() // "\xCA\xFE"
+            expect("d")
+            skipElement() // "Hello world"
+            expectEof()
+        }
+    }
+
+    /**
+     * Tests that skipping unknown keys also skips over associated tags.
+     *
+     * Includes tags on the key, tags on the value, and tags on both key and value.
+     */
+    @Test
+    fun testVerifyTags() {
+        /*
+         * A4                                 # map(4)
+         * 61                              # text(1)
+         *    61                           # "a"
+         * CC                              # tag(12)
+         *    1B FFFFFFFFFFFFFFFF          # unsigned(18446744073709551615)
+         * D8 22                           # tag(34)
+         *    61                           # text(1)
+         *       62                        # "b"
+         * 20                              # negative(0)
+         * D8 38                           # tag(56)
+         *    61                           # text(1)
+         *       63                        # "c"
+         * D8 4E                           # tag(78)
+         *    42                           # bytes(2)
+         *       CAFE                      # "\xCA\xFE"
+         * 61                              # text(1)
+         *    64                           # "d"
+         * D8 5A                           # tag(90)
+         *    CC                           # tag(12)
+         *       6B                        # text(11)
+         *          48656C6C6F20776F726C64 # "Hello world"
+         */
+        withParser("A46161CC1BFFFFFFFFFFFFFFFFD822616220D8386163D84E42CAFE6164D85ACC6B48656C6C6F20776F726C64") {
+            expectMap(size = 4)
+            expect("a")
+            skipElement(12uL) // unsigned(18446744073709551615)
+            expect("b", 34uL)
+            skipElement(null) // negative(0); explicitly setting parameter to null for clearer semantics
+            expect("c", 56uL)
+            skipElement(78uL) // "\xCA\xFE"
+            expect("d")
+            skipElement(ulongArrayOf(90uL, 12uL)) // "Hello world"
+            expectEof()
+        }
+    }
+
+    @Test
+    fun testIgnoresTagsOnStrings() {
+        /*
+         * 84                                # array(4)
+         * 68                             # text(8)
+         *    756E746167676564            # "untagged"
+         * C0                             # tag(0)
+         *    68                          # text(8)
+         *       7461676765642D30         # "tagged-0"
+         * D8 F5                          # tag(245)
+         *    6A                          # text(10)
+         *       7461676765642D323435     # "tagged-244"
+         * D9 3039                        # tag(12345)
+         *    6C                          # text(12)
+         *       7461676765642D3132333435 # "tagged-12345"
+         *
+         */
+        withParser("8468756E746167676564C0687461676765642D30D8F56A7461676765642D323435D930396C7461676765642D3132333435") {
+            assertEquals(4, startArray())
+            assertEquals("untagged", nextString())
+            assertEquals("tagged-0", nextString())
+            assertEquals("tagged-245", nextString())
+            assertEquals("tagged-12345", nextString())
+        }
+    }
+
+    @Test
+    fun testVerifyTagsOnStrings() {
+        /*
+         * 84                             # array(4)
+         * 68                             # text(8)
+         *    756E746167676564            # "untagged"
+         * C0                             # tag(0)
+         *    68                          # text(8)
+         *       7461676765642D30         # "tagged-0"
+         * D8 F5                          # tag(245)
+         *    6A                          # text(10)
+         *       7461676765642D323435     # "tagged-244"
+         * D9 3039                        # tag(12345)
+         *    6C                          # text(12)
+         *       7461676765642D3132333435 # "tagged-12345"
+         *
+         */
+        withParser("8468756E746167676564C0687461676765642D30D8F56A7461676765642D323435D930396C7461676765642D3132333435") {
+            assertEquals(4, startArray(null))
+            assertEquals("untagged", nextString(null))
+            assertEquals("tagged-0", nextString(0u))
+            assertEquals("tagged-245", nextString(245uL))
+            assertEquals("tagged-12345", nextString(12345uL))
+        }
+    }
+
+    @Test
+    fun testIgnoresTagsOnNumbers() {
+        /*
+         * 86                     # array(6)
+         * 18 7B                  # unsigned(123)
+         * C0                     # tag(0)
+         *    1A 0001E240         # unsigned(123456)
+         * D8 F5                  # tag(245)
+         *    1A 000F423F         # unsigned(999999)
+         * D9 3039                # tag(12345)
+         *    38 31               # negative(49)
+         * D8 22                  # tag(34)
+         *    FB 3FE161F9F01B866E # primitive(4603068020252444270)
+         * D9 0237                # tag(567)
+         *    FB 401999999999999A # primitive(4618891777831180698)
+         */
+        withParser("86187BC01A0001E240D8F51A000F423FD930393831D822FB3FE161F9F01B866ED90237FB401999999999999A") {
+            assertEquals(6, startArray())
+            assertEquals(123, nextNumber())
+            assertEquals(123456, nextNumber())
+            assertEquals(999999, nextNumber())
+            assertEquals(-50, nextNumber())
+            assertEquals(0.54321, nextDouble(), 0.00001)
+            assertEquals(6.4, nextDouble(), 0.00001)
+        }
+    }
+
+    @Test
+    fun testVerifiesTagsOnNumbers() {
+        /*
+         * 86                     # array(6)
+         * 18 7B                  # unsigned(123)
+         * C0                     # tag(0)
+         *    1A 0001E240         # unsigned(123456)
+         * D8 F5                  # tag(245)
+         *    1A 000F423F         # unsigned(999999)
+         * D9 3039                # tag(12345)
+         *    38 31               # negative(49)
+         * D8 22                  # tag(34)
+         *    FB 3FE161F9F01B866E # primitive(4603068020252444270)
+         * D9 0237                # tag(567)
+         *    FB 401999999999999A # primitive(4618891777831180698)
+         */
+        withParser("86187BC01A0001E240D8F51A000F423FD930393831D822FB3FE161F9F01B866ED90237FB401999999999999A") {
+            assertEquals(6, startArray(null))
+            assertEquals(123, nextNumber(null))
+            assertEquals(123456, nextNumber(0uL))
+            assertEquals(999999, nextNumber(245uL))
+            assertEquals(-50, nextNumber(12345uL))
+            assertEquals(0.54321, nextDouble(34uL), 0.00001)
+            assertEquals(6.4, nextDouble(567uL), 0.00001)
+        }
+    }
+
+    @Test
+    fun testIgnoresTagsOnArraysAndMaps() {
+        /*
+         * A2                                  # map(2)
+         * 63                                  # text(3)
+         *    6D6170                           # "map"
+         * D8 7B                               # tag(123)
+         *    A1                               # map(1)
+         *       68                            # text(8)
+         *          74686973206D6170           # "this map"
+         *       6D                            # text(13)
+         *          69732074616767656420313233 # "is tagged 123"
+         * 65                                  # text(5)
+         *    6172726179                       # "array"
+         * DA 0012D687                         # tag(1234567)
+         *    83                               # array(3)
+         *       6A                            # text(10)
+         *          74686973206172726179       # "this array"
+         *       69                            # text(9)
+         *          697320746167676564         # "is tagged"
+         *       67                            # text(7)
+         *          31323334353637             # "1234567"
+         */
+        withParser("A2636D6170D87BA16874686973206D61706D69732074616767656420313233656172726179DA0012D687836A74686973206172726179696973207461676765646731323334353637") {
+            assertEquals(2, startMap())
+            assertEquals("map", nextString())
+            assertEquals(1, startMap())
+            assertEquals("this map", nextString())
+            assertEquals("is tagged 123", nextString())
+            assertEquals("array", nextString())
+            assertEquals(3, startArray())
+            assertEquals("this array", nextString())
+            assertEquals("is tagged", nextString())
+            assertEquals("1234567", nextString())
+        }
+    }
+
+    @Test
+    fun testVerifiesTagsOnArraysAndMaps() {
+        /*
+         * A2                                  # map(2)
+         * 63                                  # text(3)
+         *    6D6170                           # "map"
+         * D8 7B                               # tag(123)
+         *    A1                               # map(1)
+         *       68                            # text(8)
+         *          74686973206D6170           # "this map"
+         *       6D                            # text(13)
+         *          69732074616767656420313233 # "is tagged 123"
+         * 65                                  # text(5)
+         *    6172726179                       # "array"
+         * DA 0012D687                         # tag(1234567)
+         *    83                               # array(3)
+         *       6A                            # text(10)
+         *          74686973206172726179       # "this array"
+         *       69                            # text(9)
+         *          697320746167676564         # "is tagged"
+         *       67                            # text(7)
+         *          31323334353637             # "1234567"
+         */
+        withParser("A2636D6170D87BA16874686973206D61706D69732074616767656420313233656172726179DA0012D687836A74686973206172726179696973207461676765646731323334353637") {
+            assertEquals(2, startMap(null))
+            assertEquals("map", nextString(null))
+            assertEquals(1, startMap(123uL))
+            assertEquals("this map", nextString(null))
+            assertEquals("is tagged 123", nextString(null))
+            assertEquals("array", nextString(null))
+            assertEquals(3, startArray(1234567uL))
+            assertEquals("this array", nextString(null))
+            assertEquals("is tagged", nextString(null))
+            assertEquals("1234567", nextString(null))
+        }
+    }
+}
+
+
+private fun CborParser.nextNumber(tag: ULong): Long = nextNumber(ulongArrayOf(tag))
+
+private fun CborParser.nextString(tag: ULong) = nextString(ulongArrayOf(tag))
+
+private fun CborParser.startArray(tag: ULong): Int = startArray(ulongArrayOf(tag))
+
+private fun CborParser.startMap(tag: ULong) = startMap(ulongArrayOf(tag))
+
+private fun CborParser.expect(expected: String, tag: ULong? = null) {
+    assertEquals(expected, actual = nextString(tag?.let { ulongArrayOf(it) }), "string")
+}
+
+private fun CborParser.expectMap(size: Int, tag: ULong? = null) {
+    assertEquals(size, actual = startMap(tag?.let { ulongArrayOf(it) }), "map size")
+}
+
+private fun CborParser.expectEof() {
+    assertTrue(isEof(), "Expected EOF.")
+}
\ No newline at end of file
diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborReaderTest.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborReaderTest.kt
deleted file mode 100644
index f615d5e..0000000
--- a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborReaderTest.kt
+++ /dev/null
@@ -1,765 +0,0 @@
-/*
- * Copyright 2017-2021 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.SimpleSealed.*
-import kotlinx.serialization.cbor.internal.*
-import kotlin.test.*
-
-class CborReaderTest {
-
-    private val ignoreUnknownKeys = Cbor { ignoreUnknownKeys = true }
-
-    private fun withDecoder(input: String, block: CborDecoder.() -> Unit) {
-        val bytes = HexConverter.parseHexBinary(input.uppercase())
-        CborDecoder(ByteArrayInput(bytes)).block()
-    }
-
-    @Test
-    fun testDecodeIntegers() {
-        withDecoder("0C1903E8") {
-            assertEquals(12L, nextNumber())
-            assertEquals(1000L, nextNumber())
-        }
-        withDecoder("203903e7") {
-            assertEquals(-1L, nextNumber())
-            assertEquals(-1000L, nextNumber())
-        }
-    }
-
-    @Test
-    fun testDecodeStrings() {
-        withDecoder("6568656C6C6F") {
-            assertEquals("hello", nextString())
-        }
-        withDecoder("7828737472696E672074686174206973206C6F6E676572207468616E2032332063686172616374657273") {
-            assertEquals("string that is longer than 23 characters", nextString())
-        }
-    }
-
-    @Test
-    fun testDecodeDoubles() {
-        withDecoder("fb7e37e43c8800759c") {
-            assertEquals(1e+300, nextDouble())
-        }
-        withDecoder("fa47c35000") {
-            assertEquals(100000.0f, nextFloat())
-        }
-    }
-
-    @Test
-    fun testDecodeSimpleObject() {
-        assertEquals(Simple("str"), Cbor.decodeFromHexString(Simple.serializer(), "bf616163737472ff"))
-    }
-
-    @Test
-    fun testDecodeComplicatedObject() {
-        val test = TypesUmbrella(
-            "Hello, world!",
-            42,
-            null,
-            listOf("a", "b"),
-            mapOf(1 to true, 2 to false),
-            Simple("lol"),
-            listOf(Simple("kek")),
-            HexConverter.parseHexBinary("cafe"),
-            HexConverter.parseHexBinary("cafe")
-        )
-        // with maps, lists & strings of indefinite length
-        assertEquals(test, Cbor.decodeFromHexString(
-            TypesUmbrella.serializer(),
-            "bf637374726d48656c6c6f2c20776f726c64216169182a686e756c6c61626c65f6646c6973749f61616162ff636d6170bf01f502f4ff65696e6e6572bf6161636c6f6cff6a696e6e6572734c6973749fbf6161636b656bffff6a62797465537472696e675f42cafeff696279746541727261799f383521ffff"
-        )
-        )
-        // with maps, lists & strings of definite length
-        assertEquals(test, Cbor.decodeFromHexString(
-            TypesUmbrella.serializer(),
-            "a9646c6973748261616162686e756c6c61626c65f6636d6170a202f401f56169182a6a696e6e6572734c69737481a16161636b656b637374726d48656c6c6f2c20776f726c642165696e6e6572a16161636c6f6c6a62797465537472696e6742cafe6962797465417272617982383521"
-        )
-        )
-    }
-
-    /**
-     * Test using example shown on page 11 of [RFC 7049 2.2.2](https://tools.ietf.org/html/rfc7049#section-2.2.2):
-     *
-     * ```
-     * 0b010_11111 0b010_00100 0xaabbccdd 0b010_00011 0xeeff99 0b111_11111
-     *
-     * 5F              -- Start indefinite-length byte string
-     *    44           -- Byte string of length 4
-     *       aabbccdd  -- Bytes content
-     *    43           -- Byte string of length 3
-     *       eeff99    -- Bytes content
-     *    FF           -- "break"
-     *
-     * After decoding, this results in a single byte string with seven
-     * bytes: 0xaabbccddeeff99.
-     * ```
-     */
-    @Test
-    fun testRfc7049IndefiniteByteStringExample() {
-        withDecoder(input = "5F44aabbccdd43eeff99FF") {
-            assertEquals(
-                expected = "aabbccddeeff99",
-                actual = HexConverter.printHexBinary(nextByteString(), lowerCase = true)
-            )
-        }
-    }
-
-    @Test
-    fun testReadByteStringWhenNullable() {
-        /* A1                         # map(1)
-         *    6A                      # text(10)
-         *       62797465537472696E67 # "byteString"
-         *    44                      # bytes(4)
-         *       01020304             # "\x01\x02\x03\x04"
-         */
-        assertEquals(
-            expected = NullableByteString(byteArrayOf(1, 2, 3, 4)),
-            actual = Cbor.decodeFromHexString(
-                deserializer = NullableByteString.serializer(),
-                hex = "a16a62797465537472696e674401020304"
-            )
-        )
-
-        /* A1                         # map(1)
-         *    6A                      # text(10)
-         *       62797465537472696E67 # "byteString"
-         *    F6                      # primitive(22)
-         */
-        assertEquals(
-            expected = NullableByteString(byteString = null),
-            actual = Cbor.decodeFromHexString(
-                deserializer = NullableByteString.serializer(),
-                hex = "a16a62797465537472696e67f6"
-            )
-        )
-    }
-
-    /**
-     * CBOR hex data represents serialized versions of [TypesUmbrella] (which does **not** have a root property 'a') so
-     * decoding to [Simple] (which has the field 'a') is expected to fail.
-     */
-    @Test
-    fun testIgnoreUnknownKeysFailsWhenCborDataIsMissingKeysThatArePresentInKotlinClass() {
-        // with maps & lists of indefinite length
-        assertFailsWithMessage<SerializationException>("Field 'a' is required") {
-            ignoreUnknownKeys.decodeFromHexString(
-                Simple.serializer(),
-                "bf637374726d48656c6c6f2c20776f726c64216169182a686e756c6c61626c65f6646c6973749f61616162ff636d6170bf01f502f4ff65696e6e6572bf6161636c6f6cff6a696e6e6572734c6973749fbf6161636b656bffffff"
-            )
-        }
-
-        // with maps & lists of definite length
-        assertFailsWithMessage<SerializationException>("Field 'a' is required") {
-            ignoreUnknownKeys.decodeFromHexString(
-                Simple.serializer(),
-                "a7646c6973748261616162686e756c6c61626c65f6636d6170a202f401f56169182a6a696e6e6572734c69737481a16161636b656b637374726d48656c6c6f2c20776f726c642165696e6e6572a16161636c6f6c"
-            )
-        }
-    }
-
-    @Test
-    fun testIgnoreUnknownKeysFailsWhenDecodingIncompleteCbor() {
-        /* A3                 # map(3)
-         *    63              # text(3)
-         *       737472       # "str"
-         *    66              # text(6)
-         *       737472696E67 # "string"
-         *    61              # text(1)
-         *       69           # "i"
-         *    00              # unsigned(0)
-         *    66              # text(6)
-         *       69676E6F7265 # "ignore"
-         * (missing value associated with "ignore" key)
-         */
-        assertFailsWithMessage<CborDecodingException>("Unexpected EOF while skipping element") {
-            ignoreUnknownKeys.decodeFromHexString(
-                TypesUmbrella.serializer(),
-                "a36373747266737472696e676169006669676e6f7265"
-            )
-        }
-
-        /* A3                 # map(3)
-         *    63              # text(3)
-         *       737472       # "str"
-         *    66              # text(6)
-         *       737472696E67 # "string"
-         *    61              # text(1)
-         *       69           # "i"
-         *    00              # unsigned(0)
-         *    66              # text(6)
-         *       69676E6F7265 # "ignore"
-         *    A2              # map(2)
-         * (missing map contents associated with "ignore" key)
-         */
-        assertFailsWithMessage<CborDecodingException>("Unexpected EOF while skipping element") {
-            ignoreUnknownKeys.decodeFromHexString(
-                TypesUmbrella.serializer(),
-                "a36373747266737472696e676169006669676e6f7265a2"
-            )
-        }
-    }
-
-    @Test
-    fun testIgnoreUnknownKeysFailsWhenEncounteringPreemptiveBreak() {
-        /* A3                 # map(3)
-         *    63              # text(3)
-         *       737472       # "str"
-         *    66              # text(6)
-         *       737472696E67 # "string"
-         *    66              # text(6)
-         *       69676E6F7265 # "ignore"
-         *    FF              # primitive(*)
-         */
-        assertFailsWithMessage<CborDecodingException>("Expected next data item, but found FF") {
-            ignoreUnknownKeys.decodeFromHexString(
-                TypesUmbrella.serializer(),
-                "a36373747266737472696e676669676e6f7265ff"
-            )
-        }
-    }
-
-    /**
-     * Tests skipping unknown keys associated with values of the following CBOR types:
-     * - Major type 0: an unsigned integer
-     * - Major type 1: a negative integer
-     * - Major type 2: a byte string
-     * - Major type 3: a text string
-     */
-    @Test
-    fun testSkipPrimitives() {
-        /* A4                           # map(4)
-         *    61                        # text(1)
-         *       61                     # "a"
-         *    1B FFFFFFFFFFFFFFFF       # unsigned(18446744073709551615)
-         *    61                        # text(1)
-         *       62                     # "b"
-         *    20                        # negative(0)
-         *    61                        # text(1)
-         *       63                     # "c"
-         *    42                        # bytes(2)
-         *       CAFE                   # "\xCA\xFE"
-         *    61                        # text(1)
-         *       64                     # "d"
-         *    6B                        # text(11)
-         *       48656C6C6F20776F726C64 # "Hello world"
-         */
-        withDecoder("a461611bffffffffffffffff616220616342cafe61646b48656c6c6f20776f726c64") {
-            expectMap(size = 4)
-            expect("a")
-            skipElement() // unsigned(18446744073709551615)
-            expect("b")
-            skipElement() // negative(0)
-            expect("c")
-            skipElement() // "\xCA\xFE"
-            expect("d")
-            skipElement() // "Hello world"
-            expectEof()
-        }
-    }
-
-    /**
-     * Tests skipping unknown keys associated with values (that are empty) of the following CBOR types:
-     * - Major type 2: a byte string
-     * - Major type 3: a text string
-     */
-    @Test
-    fun testSkipEmptyPrimitives() {
-        /* A2       # map(2)
-         *    61    # text(1)
-         *       61 # "a"
-         *    40    # bytes(0)
-         *          # ""
-         *    61    # text(1)
-         *       62 # "b"
-         *    60    # text(0)
-         *          # ""
-         */
-        withDecoder("a2616140616260") {
-            expectMap(size = 2)
-            expect("a")
-            skipElement() // bytes(0)
-            expect("b")
-            skipElement() // text(0)
-            expectEof()
-        }
-    }
-
-    /**
-     * Tests skipping unknown keys associated with values of the following CBOR types:
-     * - Major type 4: an array of data items
-     * - Major type 5: a map of pairs of data items
-     */
-    @Test
-    fun testSkipCollections() {
-        /* A2                                  # map(2)
-         *    61                               # text(1)
-         *       61                            # "a"
-         *    83                               # array(3)
-         *       01                            # unsigned(1)
-         *       18 FF                         # unsigned(255)
-         *       1A 00010000                   # unsigned(65536)
-         *    61                               # text(1)
-         *       62                            # "b"
-         *    A2                               # map(2)
-         *       61                            # text(1)
-         *          78                         # "x"
-         *       67                            # text(7)
-         *          6B6F746C696E78             # "kotlinx"
-         *       61                            # text(1)
-         *          79                         # "y"
-         *       6D                            # text(13)
-         *          73657269616C697A6174696F6E # "serialization"
-         */
-        withDecoder("a26161830118ff1a000100006162a26178676b6f746c696e7861796d73657269616c697a6174696f6e") {
-            expectMap(size = 2)
-            expect("a")
-            skipElement() // [1, 255, 65536]
-            expect("b")
-            skipElement() // {"x": "kotlinx", "y": "serialization"}
-            expectEof()
-        }
-    }
-
-    /**
-     * Tests skipping unknown keys associated with values (empty collections) of the following CBOR types:
-     * - Major type 4: an array of data items
-     * - Major type 5: a map of pairs of data items
-     */
-    @Test
-    fun testSkipEmptyCollections() {
-        /* A2       # map(2)
-         *    61    # text(1)
-         *       61 # "a"
-         *    80    # array(0)
-         *    61    # text(1)
-         *       62 # "b"
-         *    A0    # map(0)
-         */
-        withDecoder("a26161806162a0") {
-            expectMap(size = 2)
-            expect("a")
-            skipElement() // [1, 255, 65536]
-            expect("b")
-            skipElement() // {"x": "kotlinx", "y": "serialization"}
-            expectEof()
-        }
-    }
-
-    /**
-     * Tests skipping unknown keys associated with **indefinite length** values of the following CBOR types:
-     * - Major type 2: a byte string
-     * - Major type 3: a text string
-     * - Major type 4: an array of data items
-     * - Major type 5: a map of pairs of data items
-     */
-    @Test
-    fun testSkipIndefiniteLength() {
-        /* A4                                  # map(4)
-         *    61                               # text(1)
-         *       61                            # "a"
-         *    5F                               # bytes(*)
-         *       42                            # bytes(2)
-         *          CAFE                       # "\xCA\xFE"
-         *       43                            # bytes(3)
-         *          010203                     # "\x01\x02\x03"
-         *       FF                            # primitive(*)
-         *    61                               # text(1)
-         *       62                            # "b"
-         *    7F                               # text(*)
-         *       66                            # text(6)
-         *          48656C6C6F20               # "Hello "
-         *       65                            # text(5)
-         *          776F726C64                 # "world"
-         *       FF                            # primitive(*)
-         *    61                               # text(1)
-         *       63                            # "c"
-         *    9F                               # array(*)
-         *       67                            # text(7)
-         *          6B6F746C696E78             # "kotlinx"
-         *       6D                            # text(13)
-         *          73657269616C697A6174696F6E # "serialization"
-         *       FF                            # primitive(*)
-         *    61                               # text(1)
-         *       64                            # "d"
-         *    BF                               # map(*)
-         *       61                            # text(1)
-         *          31                         # "1"
-         *       01                            # unsigned(1)
-         *       61                            # text(1)
-         *          32                         # "2"
-         *       02                            # unsigned(2)
-         *       61                            # text(1)
-         *          33                         # "3"
-         *       03                            # unsigned(3)
-         *       FF                            # primitive(*)
-         */
-        withDecoder("a461615f42cafe43010203ff61627f6648656c6c6f2065776f726c64ff61639f676b6f746c696e786d73657269616c697a6174696f6eff6164bf613101613202613303ff") {
-            expectMap(size = 4)
-            expect("a")
-            skipElement() // "\xCA\xFE\x01\x02\x03"
-            expect("b")
-            skipElement() // "Hello world"
-            expect("c")
-            skipElement() // ["kotlinx", "serialization"]
-            expect("d")
-            skipElement() // {"1": 1, "2": 2, "3": 3}
-            expectEof()
-        }
-    }
-
-    /**
-     * Tests that skipping unknown keys also skips over associated tags.
-     *
-     * Includes tags on the key, tags on the value, and tags on both key and value.
-     */
-    @Test
-    fun testSkipTags() {
-        /*
-         * A4                                 # map(4)
-         * 61                              # text(1)
-         *    61                           # "a"
-         * CC                              # tag(12)
-         *    1B FFFFFFFFFFFFFFFF          # unsigned(18446744073709551615)
-         * D8 22                           # tag(34)
-         *    61                           # text(1)
-         *       62                        # "b"
-         * 20                              # negative(0)
-         * D8 38                           # tag(56)
-         *    61                           # text(1)
-         *       63                        # "c"
-         * D8 4E                           # tag(78)
-         *    42                           # bytes(2)
-         *       CAFE                      # "\xCA\xFE"
-         * 61                              # text(1)
-         *    64                           # "d"
-         * D8 5A                           # tag(90)
-         *    CC                           # tag(12)
-         *       6B                        # text(11)
-         *          48656C6C6F20776F726C64 # "Hello world"
-         */
-        withDecoder("A46161CC1BFFFFFFFFFFFFFFFFD822616220D8386163D84E42CAFE6164D85ACC6B48656C6C6F20776F726C64") {
-            expectMap(size = 4)
-            expect("a")
-            skipElement() // unsigned(18446744073709551615)
-            expect("b")
-            skipElement() // negative(0)
-            expect("c")
-            skipElement() // "\xCA\xFE"
-            expect("d")
-            skipElement() // "Hello world"
-            expectEof()
-        }
-    }
-
-    @Test
-    fun testDecodeCborWithUnknownField() {
-        assertEquals(
-            expected = Simple("123"),
-            actual = ignoreUnknownKeys.decodeFromHexString(
-                deserializer = Simple.serializer(),
-
-                /* BF           # map(*)
-                 *    61        # text(1)
-                 *       61     # "a"
-                 *    63        # text(3)
-                 *       313233 # "123"
-                 *    61        # text(1)
-                 *       62     # "b"
-                 *    63        # text(3)
-                 *       393837 # "987"
-                 *    FF        # primitive(*)
-                 */
-                hex = "bf616163313233616263393837ff"
-            )
-        )
-    }
-
-    @Test
-    fun testDecodeCborWithUnknownNestedIndefiniteFields() {
-        assertEquals(
-            expected = Simple("123"),
-            actual = ignoreUnknownKeys.decodeFromHexString(
-                deserializer = Simple.serializer(),
-
-                /* BF             # map(*)
-                 *    61          # text(1)
-                 *       61       # "a"
-                 *    63          # text(3)
-                 *       313233   # "123"
-                 *    61          # text(1)
-                 *       62       # "b"
-                 *    BF          # map(*)
-                 *       7F       # text(*)
-                 *          61    # text(1)
-                 *             78 # "x"
-                 *          FF    # primitive(*)
-                 *       A1       # map(1)
-                 *          61    # text(1)
-                 *             79 # "y"
-                 *          0A    # unsigned(10)
-                 *       FF       # primitive(*)
-                 *    61          # text(1)
-                 *       63       # "c"
-                 *    9F          # array(*)
-                 *       01       # unsigned(1)
-                 *       02       # unsigned(2)
-                 *       03       # unsigned(3)
-                 *       FF       # primitive(*)
-                 *    FF          # primitive(*)
-                 */
-                hex = "bf6161633132336162bf7f6178ffa161790aff61639f010203ffff"
-            )
-        )
-    }
-
-    /**
-     * The following CBOR diagnostic output demonstrates the additional fields (prefixed with `+` in front of each line)
-     * present in the encoded CBOR data that does not have associated fields in the Kotlin classes (they will be skipped
-     * over with `ignoreUnknownKeys` is enabled).
-     *
-     * ```diff
-     *   {
-     * +   "extra": [
-     * +     9,
-     * +     8,
-     * +     7
-     * +   ],
-     *     "boxed": [
-     *       [
-     *         "kotlinx.serialization.SimpleSealed.SubSealedA",
-     *         {
-     *           "s": "a",
-     * +         "newA": {
-     * +           "x": 1,
-     * +           "y": 2
-     * +         }
-     *         }
-     *       ],
-     *       [
-     *         "kotlinx.serialization.SimpleSealed.SubSealedB",
-     *         {
-     *           "i": 1
-     *         }
-     *       ]
-     *     ]
-     *   }
-     * ```
-     */
-    @Test
-    fun testDecodeCborWithUnknownKeysInSealedClasses() {
-        /* BF                      # map(*)
-         *    65                   # text(5)
-         *       6578747261        # "extra"
-         *    83                   # array(3)
-         *       09                # unsigned(9)
-         *       08                # unsigned(8)
-         *       07                # unsigned(7)
-         *    65                   # text(5)
-         *       626F786564        # "boxed"
-         *    9F                   # array(*)
-         *       9F                # array(*)
-         *          78 2D          # text(45)
-         *             6B6F746C696E782E73657269616C697A6174696F6E2E53696D706C655365616C65642E5375625365616C656441 # "kotlinx.serialization.SimpleSealed.SubSealedA"
-         *          BF             # map(*)
-         *             61          # text(1)
-         *                73       # "s"
-         *             61          # text(1)
-         *                61       # "a"
-         *             64          # text(4)
-         *                6E657741 # "newA"
-         *             BF          # map(*)
-         *                61       # text(1)
-         *                   78    # "x"
-         *                01       # unsigned(1)
-         *                61       # text(1)
-         *                   79    # "y"
-         *                02       # unsigned(2)
-         *                FF       # primitive(*)
-         *             FF          # primitive(*)
-         *          FF             # primitive(*)
-         *       9F                # array(*)
-         *          78 2D          # text(45)
-         *             6B6F746C696E782E73657269616C697A6174696F6E2E53696D706C655365616C65642E5375625365616C656442 # "kotlinx.serialization.SimpleSealed.SubSealedB"
-         *          BF             # map(*)
-         *             61          # text(1)
-         *                69       # "i"
-         *             01          # unsigned(1)
-         *             FF          # primitive(*)
-         *          FF             # primitive(*)
-         *       FF                # primitive(*)
-         *    FF                   # primitive(*)
-         */
-
-        assertEquals(
-            expected = SealedBox(
-                listOf(
-                    SubSealedA("a"),
-                    SubSealedB(1)
-                )
-            ),
-            actual = ignoreUnknownKeys.decodeFromHexString(
-                SealedBox.serializer(),
-                "bf6565787472618309080765626f7865649f9f782d6b6f746c696e782e73657269616c697a6174696f6e2e53696d706c655365616c65642e5375625365616c656441bf61736161646e657741bf617801617902ffffff9f782d6b6f746c696e782e73657269616c697a6174696f6e2e53696d706c655365616c65642e5375625365616c656442bf616901ffffffff"
-            )
-        )
-    }
-
-    @Test
-    fun testReadCustomByteString() {
-        assertEquals(
-                expected = TypeWithCustomByteString(CustomByteString(0x11, 0x22, 0x33)),
-                actual = Cbor.decodeFromHexString("bf617843112233ff")
-        )
-    }
-
-    @Test
-    fun testReadNullableCustomByteString() {
-        assertEquals(
-                expected = TypeWithNullableCustomByteString(CustomByteString(0x11, 0x22, 0x33)),
-                actual = Cbor.decodeFromHexString("bf617843112233ff")
-        )
-    }
-
-    @Test
-    fun testReadNullCustomByteString() {
-        assertEquals(
-                expected = TypeWithNullableCustomByteString(null),
-                actual = Cbor.decodeFromHexString("bf6178f6ff")
-        )
-    }
-
-    @Test
-    fun testReadValueClassWithByteString() {
-        assertContentEquals(
-            expected = byteArrayOf(0x11, 0x22, 0x33),
-            actual = Cbor.decodeFromHexString<ValueClassWithByteString>("43112233").x
-        )
-    }
-
-    @Test
-    fun testReadValueClassCustomByteString() {
-        assertEquals(
-            expected = ValueClassWithCustomByteString(CustomByteString(0x11, 0x22, 0x33)),
-            actual = Cbor.decodeFromHexString("43112233")
-        )
-    }
-
-    @Test
-    fun testReadValueClassWithUnlabeledByteString() {
-        assertContentEquals(
-            expected = byteArrayOf(
-                0x11,
-                0x22,
-                0x33
-            ),
-            actual = Cbor.decodeFromHexString<ValueClassWithUnlabeledByteString>("43112233").x.x
-        )
-    }
-
-    @Test
-    fun testIgnoresTagsOnStrings() {
-        /*
-         * 84                                # array(4)
-         * 68                             # text(8)
-         *    756E746167676564            # "untagged"
-         * C0                             # tag(0)
-         *    68                          # text(8)
-         *       7461676765642D30         # "tagged-0"
-         * D8 F5                          # tag(245)
-         *    6A                          # text(10)
-         *       7461676765642D323435     # "tagged-244"
-         * D9 3039                        # tag(12345)
-         *    6C                          # text(12)
-         *       7461676765642D3132333435 # "tagged-12345"
-         *
-         */
-        withDecoder("8468756E746167676564C0687461676765642D30D8F56A7461676765642D323435D930396C7461676765642D3132333435") {
-            assertEquals(4, startArray())
-            assertEquals("untagged", nextString())
-            assertEquals("tagged-0", nextString())
-            assertEquals("tagged-245", nextString())
-            assertEquals("tagged-12345", nextString())
-        }
-    }
-
-    @Test
-    fun testIgnoresTagsOnNumbers() {
-        /*
-         * 86                     # array(6)
-         * 18 7B                  # unsigned(123)
-         * C0                     # tag(0)
-         *    1A 0001E240         # unsigned(123456)
-         * D8 F5                  # tag(245)
-         *    1A 000F423F         # unsigned(999999)
-         * D9 3039                # tag(12345)
-         *    38 31               # negative(49)
-         * D8 22                  # tag(34)
-         *    FB 3FE161F9F01B866E # primitive(4603068020252444270)
-         * D9 0237                # tag(567)
-         *    FB 401999999999999A # primitive(4618891777831180698)
-         */
-        withDecoder("86187BC01A0001E240D8F51A000F423FD930393831D822FB3FE161F9F01B866ED90237FB401999999999999A") {
-            assertEquals(6, startArray())
-            assertEquals(123, nextNumber())
-            assertEquals(123456, nextNumber())
-            assertEquals(999999, nextNumber())
-            assertEquals(-50, nextNumber())
-            assertEquals(0.54321, nextDouble(), 0.00001)
-            assertEquals(6.4, nextDouble(), 0.00001)
-        }
-    }
-
-    @Test
-    fun testIgnoresTagsOnArraysAndMaps() {
-        /*
-         * A2                                  # map(2)
-         * 63                                  # text(3)
-         *    6D6170                           # "map"
-         * D8 7B                               # tag(123)
-         *    A1                               # map(1)
-         *       68                            # text(8)
-         *          74686973206D6170           # "this map"
-         *       6D                            # text(13)
-         *          69732074616767656420313233 # "is tagged 123"
-         * 65                                  # text(5)
-         *    6172726179                       # "array"
-         * DA 0012D687                         # tag(1234567)
-         *    83                               # array(3)
-         *       6A                            # text(10)
-         *          74686973206172726179       # "this array"
-         *       69                            # text(9)
-         *          697320746167676564         # "is tagged"
-         *       67                            # text(7)
-         *          31323334353637             # "1234567"
-         */
-        withDecoder("A2636D6170D87BA16874686973206D61706D69732074616767656420313233656172726179DA0012D687836A74686973206172726179696973207461676765646731323334353637") {
-            assertEquals(2, startMap())
-            assertEquals("map", nextString())
-            assertEquals(1, startMap())
-            assertEquals("this map", nextString())
-            assertEquals("is tagged 123", nextString())
-            assertEquals("array", nextString())
-            assertEquals(3, startArray())
-            assertEquals("this array", nextString())
-            assertEquals("is tagged", nextString())
-            assertEquals("1234567", nextString())
-        }
-    }
-}
-
-private fun CborDecoder.expect(expected: String) {
-    assertEquals(expected, actual = nextString(), "string")
-}
-
-private fun CborDecoder.expectMap(size: Int) {
-    assertEquals(size, actual = startMap(), "map size")
-}
-
-private fun CborDecoder.expectEof() {
-    assertTrue(isEof(), "Expected EOF.")
-}
diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborRootLevelNullsTest.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborRootLevelNullsTest.kt
index 3e54834..0106ccc 100644
--- a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborRootLevelNullsTest.kt
+++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborRootLevelNullsTest.kt
@@ -15,7 +15,9 @@
     @Test
     fun testNull() {
         val obj: Simple? = null
-        val content = Cbor.encodeToByteArray(Simple.serializer().nullable, obj)
-        assertTrue(content.contentEquals(byteArrayOf(0xf6.toByte())))
+        listOf(Cbor, Cbor { useDefiniteLengthEncoding = true }).forEach {
+            val content = it.encodeToByteArray(Simple.serializer().nullable, obj)
+            assertTrue(content.contentEquals(byteArrayOf(0xf6.toByte())))
+        }
     }
 }
diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborTaggedTest.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborTaggedTest.kt
new file mode 100644
index 0000000..fa884c0
--- /dev/null
+++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborTaggedTest.kt
@@ -0,0 +1,808 @@
+/*
+ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:OptIn(ExperimentalUnsignedTypes::class)
+
+package kotlinx.serialization.cbor
+
+import kotlinx.serialization.*
+import kotlinx.serialization.cbor.internal.*
+import kotlin.test.*
+
+
+@Serializable
+data class DataWithTags(
+    @ValueTags(12uL)
+    val a: ULong,
+
+    @KeyTags(34uL)
+    val b: Int,
+
+    @KeyTags(56uL)
+    @ValueTags(78uL)
+    @ByteString val c: ByteArray,
+
+    @ValueTags(90uL, 12uL)
+    val d: String
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null || this::class != other::class) return false
+
+        other as DataWithTags
+
+        if (a != other.a) return false
+        if (b != other.b) return false
+        if (!c.contentEquals(other.c)) return false
+        return d == other.d
+    }
+
+    override fun hashCode(): Int {
+        var result = a.hashCode()
+        result = 31 * result + b
+        result = 31 * result + c.contentHashCode()
+        result = 31 * result + d.hashCode()
+        return result
+    }
+}
+
+class CborTaggedTest {
+
+    private val reference = DataWithTags(
+        a = 0xFFFFFFFuL,
+        b = -1,
+        c = byteArrayOf(0xCA.toByte(), 0xFE.toByte()),
+        d = "Hello World"
+    )
+
+    /*
+     * BF                                 # map(*)
+     *    61                              # text(1)
+     *       61                           # "a"
+     *    CC                              # tag(12)
+     *       1A 0FFFFFFF                  # unsigned(268435455)
+     *    D8 22                           # tag(34)
+     *       61                           # text(1)
+     *          62                        # "b"
+     *    20                              # negative(0)
+     *    D8 38                           # tag(56)
+     *       61                           # text(1)
+     *          63                        # "c"
+     *    D8 4E                           # tag(78)
+     *       42                           # bytes(2)
+     *          CAFE                      # "\xCA\xFE"
+     *    61                              # text(1)
+     *       64                           # "d"
+     *    D8 5A                           # tag(90)
+     *       CC                           # tag(12)
+     *          6B                        # text(11)
+     *             48656C6C6F20576F726C64 # "Hello World"
+     *    FF                              # primitive(*)
+     */
+    private val referenceHexString =
+        "bf6161cc1a0fffffffd822616220d8386163d84e42cafe6164d85acc6b48656c6c6f20576f726c64ff"
+
+    /*
+     * A4                                 # map(4)
+     *    61                              # text(1)
+     *       61                           # "a"
+     *    CC                              # tag(12)
+     *       1A 0FFFFFFF                  # unsigned(268435455)
+     *    D8 22                           # tag(34)
+     *       61                           # text(1)
+     *          62                        # "b"
+     *    20                              # negative(0)
+     *    D8 38                           # tag(56)
+     *       61                           # text(1)
+     *          63                        # "c"
+     *    D8 4E                           # tag(78)
+     *       42                           # bytes(2)
+     *          CAFE                      # "\xCA\xFE"
+     *    61                              # text(1)
+     *       64                           # "d"
+     *    D8 5A                           # tag(90)
+     *       CC                           # tag(12)
+     *          6B                        # text(11)
+     *             48656C6C6F20576F726C64 # "Hello World"
+     */
+    private val referenceHexStringDefLen =
+        "a46161cc1a0fffffffd822616220d8386163d84e42cafe6164d85acc6b48656c6c6f20576f726c64"
+
+    /*
+     * BF                                 # map(*)
+     *    61                              # text(1)
+     *       61                           # "a"
+     *    CC                              # tag(12)
+     *       1A 0FFFFFFF                  # unsigned(268435455)
+     *    61                              # text(1)
+     *       62                           # "b"
+     *    20                              # negative(0)
+     *    61                              # text(1)
+     *       63                           # "c"
+     *    D8 4E                           # tag(78)
+     *       42                           # bytes(2)
+     *          CAFE                      # "\xCA\xFE"
+     *    61                              # text(1)
+     *       64                           # "d"
+     *    D8 5A                           # tag(90)
+     *       CC                           # tag(12)
+     *          6B                        # text(11)
+     *             48656C6C6F20576F726C64 # "Hello World"
+     *    FF                              # primitive(*)
+     */
+    private val noKeyTags = "bf6161cc1a0fffffff6162206163d84e42cafe6164d85acc6b48656c6c6f20576f726c64ff"
+
+    /*
+     * A4                                 # map(4)
+     *    61                              # text(1)
+     *       61                           # "a"
+     *    CC                              # tag(12)
+     *       1A 0FFFFFFF                  # unsigned(268435455)
+     *    61                              # text(1)
+     *       62                           # "b"
+     *    20                              # negative(0)
+     *    61                              # text(1)
+     *       63                           # "c"
+     *    D8 4E                           # tag(78)
+     *       42                           # bytes(2)
+     *          CAFE                      # "\xCA\xFE"
+     *    61                              # text(1)
+     *       64                           # "d"
+     *    D8 5A                           # tag(90)
+     *       CC                           # tag(12)
+     *          6B                        # text(11)
+     *             48656C6C6F20576F726C64 # "Hello World"
+     *
+     *
+     */
+    private val noKeyTagsDefLen = "a46161cc1a0fffffff6162206163d84e42cafe6164d85acc6b48656c6c6f20576f726c64"
+
+    /*
+     * BF                           # map(*)
+     *    61                        # text(1)
+     *       61                     # "a"
+     *    1A 0FFFFFFF               # unsigned(268435455)
+     *    D8 22                     # tag(34)
+     *       61                     # text(1)
+     *          62                  # "b"
+     *    20                        # negative(0)
+     *    D8 38                     # tag(56)
+     *       61                     # text(1)
+     *          63                  # "c"
+     *    42                        # bytes(2)
+     *       CAFE                   # "\xCA\xFE"
+     *    61                        # text(1)
+     *       64                     # "d"
+     *    6B                        # text(11)
+     *       48656C6C6F20576F726C64 # "Hello World"
+     *    FF                        # primitive(*)
+     */
+    private val noValueTags = "bf61611a0fffffffd822616220d838616342cafe61646b48656c6c6f20576f726c64ff"
+
+    /*
+     * BF                           # map(*)
+     *    61                        # text(1)
+     *       61                     # "a"
+     *    1A 0FFFFFFF               # unsigned(268435455)
+     *    61                        # text(1)
+     *       62                     # "b"
+     *    20                        # negative(0)
+     *    61                        # text(1)
+     *       63                     # "c"
+     *    42                        # bytes(2)
+     *       CAFE                   # "\xCA\xFE"
+     *    61                        # text(1)
+     *       64                     # "d"
+     *    6B                        # text(11)
+     *       48656C6C6F20576F726C64 # "Hello World"
+     *    FF                        # primitive(*)
+     *
+     */
+    private val noTags = "bf61611a0fffffff616220616342cafe61646b48656c6c6f20576f726c64ff"
+
+    /*
+     * A4                           # map(4)
+     *    61                        # text(1)
+     *       61                     # "a"
+     *    1A 0FFFFFFF               # unsigned(268435455)
+     *    61                        # text(1)
+     *       62                     # "b"
+     *    20                        # negative(0)
+     *    61                        # text(1)
+     *       63                     # "c"
+     *    42                        # bytes(2)
+     *       CAFE                   # "\xCA\xFE"
+     *    61                        # text(1)
+     *       64                     # "d"
+     *    6B                        # text(11)
+     *       48656C6C6F20576F726C64 # "Hello World"
+     *
+     */
+    private val noTagsDefLen = "a461611a0fffffff616220616342cafe61646b48656c6c6f20576f726c64"
+
+    @Test
+    fun writeReadVerifyTaggedClass() {
+        assertEquals(referenceHexString, Cbor {
+            useDefiniteLengthEncoding = false
+            encodeKeyTags = true
+            encodeValueTags = true
+            encodeObjectTags = true
+            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(reference, Cbor.CoseCompliant.decodeFromHexString(DataWithTags.serializer(), referenceHexString))
+        assertEquals(
+            reference,
+            Cbor.CoseCompliant.decodeFromHexString(DataWithTags.serializer(), referenceHexStringDefLen)
+        )
+    }
+
+    @Test
+    fun writeReadUntaggedKeys() {
+        assertEquals(noKeyTags, 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
+            encodeValueTags = true
+            encodeObjectTags = true
+            verifyValueTags = true
+            verifyObjectTags = true
+            verifyKeyTags = false
+        }.decodeFromHexString(noKeyTagsDefLen))
+        assertEquals(reference, Cbor {
+            encodeKeyTags = true
+            encodeValueTags = true
+            encodeObjectTags = true
+            verifyValueTags = true
+            verifyObjectTags = true
+            verifyKeyTags = false
+        }.decodeFromHexString(referenceHexString))
+
+        assertFailsWith(CborDecodingException::class) {
+            Cbor.CoseCompliant.decodeFromHexString(
+                DataWithTags.serializer(),
+                noKeyTags
+            )
+        }
+
+        assertFailsWith(CborDecodingException::class) {
+            Cbor {
+                encodeKeyTags = true
+                encodeValueTags = true
+                encodeObjectTags = true
+                verifyValueTags = true
+                verifyObjectTags = true
+                verifyKeyTags = false
+            }.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 {
+            encodeKeyTags = true
+            encodeValueTags = true
+            encodeObjectTags = true
+            verifyKeyTags = true
+            verifyObjectTags = true
+            verifyValueTags = false
+        }.decodeFromHexString(noValueTags))
+        assertEquals(reference, Cbor {
+            encodeKeyTags = true
+            encodeValueTags = true
+            encodeObjectTags = true
+            verifyKeyTags = true
+            verifyObjectTags = true
+            verifyValueTags = false
+        }.decodeFromHexString(referenceHexString))
+
+        assertFailsWith(CborDecodingException::class) {
+            Cbor {
+                encodeKeyTags = true
+                encodeValueTags = true
+                encodeObjectTags = true
+                verifyKeyTags = true
+                verifyValueTags = true
+                verifyObjectTags = true
+            }.decodeFromHexString(
+                DataWithTags.serializer(),
+                noValueTags
+            )
+        }
+
+        assertFailsWith(CborDecodingException::class) {
+            Cbor {
+                encodeKeyTags = true
+                encodeValueTags = true
+                encodeObjectTags = true
+                verifyKeyTags = true
+                verifyObjectTags = true
+                verifyValueTags = false
+            }.decodeFromHexString(
+                DataWithTags.serializer(),
+                noKeyTags
+            )
+        }
+
+    }
+
+    @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
+            encodeObjectTags = true
+            verifyObjectTags = true
+            verifyKeyTags = false
+            verifyValueTags = false
+        }.decodeFromHexString(noTags))
+
+        assertEquals(reference, Cbor {
+            encodeKeyTags = true
+            encodeValueTags = true
+            encodeObjectTags = true
+            verifyObjectTags = true
+            verifyKeyTags = false
+            verifyValueTags = false
+        }.decodeFromHexString(noTagsDefLen))
+
+        assertEquals(reference, Cbor {
+            encodeKeyTags = true
+            encodeValueTags = true
+            encodeObjectTags = true
+            verifyObjectTags = true
+            verifyKeyTags = false
+            verifyValueTags = false
+            useDefiniteLengthEncoding = true
+        }.decodeFromHexString(noTags))
+
+        assertEquals(reference, Cbor {
+            encodeKeyTags = true
+            encodeValueTags = true
+            encodeObjectTags = true
+            verifyObjectTags = true
+            verifyKeyTags = false
+            verifyValueTags = false
+            useDefiniteLengthEncoding = true
+        }.decodeFromHexString(noTagsDefLen))
+
+        assertEquals(reference, Cbor {
+            encodeKeyTags = true
+            encodeValueTags = true
+            encodeObjectTags = true
+            verifyObjectTags = true
+            verifyKeyTags = false
+            verifyValueTags = false
+        }.decodeFromHexString(noKeyTags))
+
+        assertEquals(reference, Cbor {
+            encodeKeyTags = true
+            encodeValueTags = true
+            encodeObjectTags = true
+            verifyObjectTags = true
+            verifyKeyTags = false
+            verifyValueTags = false
+        }.decodeFromHexString(noValueTags))
+
+        assertEquals(reference, Cbor {
+            encodeKeyTags = true
+            encodeValueTags = true
+            encodeObjectTags = true
+            verifyObjectTags = true
+            verifyKeyTags = false
+            verifyValueTags = false
+        }.decodeFromHexString(referenceHexString))
+
+        assertFailsWith(CborDecodingException::class) {
+            Cbor {
+                encodeKeyTags = true
+                encodeValueTags = true
+                encodeObjectTags = true
+                verifyKeyTags = true
+                verifyValueTags = true
+                verifyObjectTags = true
+            }.decodeFromHexString(
+                DataWithTags.serializer(),
+                noTags
+            )
+        }
+
+    }
+
+    @Test
+    fun wrongTags() {
+        val wrongTag55ForPropertyC = "A46161CC1A0FFFFFFFD822616220D8376163D84E42CAFE6164D85ACC6B48656C6C6F20576F726C64"
+        listOf(
+            Cbor {
+                encodeKeyTags = true
+                encodeValueTags = true
+                encodeObjectTags = true
+                verifyKeyTags = true
+                verifyValueTags = true
+                verifyObjectTags = true
+            },
+            Cbor {
+                encodeKeyTags = true
+                encodeValueTags = true
+                encodeObjectTags = true
+                verifyKeyTags = true
+                verifyValueTags = true
+                verifyObjectTags = true
+                useDefiniteLengthEncoding = true
+            }).forEach { cbor ->
+
+            assertContains(
+                assertFailsWith(
+                    CborDecodingException::class,
+                    message = "CBOR tags [55] do not match declared tags [56]"
+                ) {
+                    cbor.decodeFromHexString(
+                        DataWithTags.serializer(),
+                        wrongTag55ForPropertyC
+                    )
+                }.message ?: "", "CBOR tags [55] do not match expected tags [56]"
+            )
+        }
+        listOf(
+            Cbor {
+                encodeKeyTags = true
+                encodeValueTags = true
+                encodeObjectTags = true
+                verifyValueTags = true
+                verifyObjectTags = true
+                verifyKeyTags = false
+                useDefiniteLengthEncoding = true
+            },
+            Cbor {
+                encodeKeyTags = true
+                encodeValueTags = true
+                encodeObjectTags = true
+                verifyValueTags = true
+                verifyObjectTags = true
+                verifyKeyTags = false
+            }).forEach { cbor ->
+            assertEquals(reference, cbor.decodeFromHexString(wrongTag55ForPropertyC))
+        }
+    }
+
+
+    @Test
+    fun objectTags() {
+        /**
+         * D9 0539         # tag(1337)
+         *    BF           # map(*)
+         *       63        # text(3)
+         *          616C67 # "alg"
+         *       13        # unsigned(19)
+         *       FF        # primitive(*)
+         */
+        val referenceHexString = "d90539bf63616c6713ff"
+        val untaggedHexString = "bf63616c6713ff" //no ObjectTags
+        val reference = ClassAsTagged(19)
+
+        val cbor = Cbor {
+            encodeKeyTags = true
+            encodeValueTags = true
+            verifyKeyTags = true
+            verifyValueTags = true
+            useDefiniteLengthEncoding = false
+            verifyObjectTags = true
+            encodeObjectTags = true
+        }
+
+        assertEquals(referenceHexString, cbor.encodeToHexString(ClassAsTagged.serializer(), reference))
+        assertEquals(reference, cbor.decodeFromHexString(ClassAsTagged.serializer(), referenceHexString))
+
+        assertEquals(
+            reference,
+            Cbor { verifyObjectTags = false }.decodeFromHexString(ClassAsTagged.serializer(), referenceHexString)
+        )
+
+        assertEquals(
+            untaggedHexString,
+            Cbor { encodeObjectTags = false }.encodeToHexString(ClassAsTagged.serializer(), reference)
+        )
+
+
+        assertEquals(
+            reference,
+            Cbor { verifyObjectTags = false }.decodeFromHexString(ClassAsTagged.serializer(), untaggedHexString)
+        )
+
+        assertContains(
+            assertFailsWith(CborDecodingException::class) {
+                cbor.decodeFromHexString(ClassAsTagged.serializer(), untaggedHexString)
+            }.message ?: "", "do not match expected tags"
+        )
+
+        /**
+         * 81                 # array(1)
+         *    D9 0539         # tag(1337)
+         *       A1           # map(1)
+         *          63        # text(3)
+         *             616C67 # "alg"
+         *          13        # unsigned(19)
+         */
+        val listOfObjectTagged = listOf(reference)
+        assertEquals("81d90539a163616c6713", Cbor.CoseCompliant.encodeToHexString(listOfObjectTagged))
+
+
+    }
+
+
+    @Test
+    fun nestedObjectTags() {
+        /**
+         * BF                                 # map(*)
+         *    63                              # text(3)
+         *       616C67                       # "alg"
+         *    0D                              # unsigned(13)
+         *    64                              # text(4)
+         *       696E7473                     # "ints"
+         *    D3                              # tag(19)
+         *       9F                           # array(*)
+         *          18 1A                     # unsigned(26)
+         *          18 18                     # unsigned(24)
+         *          FF                        # primitive(*)
+         *    69                              # text(9)
+         *       6F626A546167676564           # "objTagged"
+         *    D8 2A                           # tag(42)
+         *       D9 0539                      # tag(1337)
+         *          BF                        # map(*)
+         *             63                     # text(3)
+         *                616C67              # "alg"
+         *             13                     # unsigned(19)
+         *             FF                     # primitive(*)
+         *    6E                              # text(14)
+         *       6F626A5461676765644172726179 # "objTaggedArray"
+         *    D8 2A                           # tag(42)
+         *       9F                           # array(*)
+         *          D9 0539                   # tag(1337)
+         *             BF                     # map(*)
+         *                63                  # text(3)
+         *                   616C67           # "alg"
+         *                19 03E8             # unsigned(1000)
+         *                FF                  # primitive(*)
+         *          FF                        # primitive(*)
+         *    FF                              # primitive(*)
+         */
+        val referenceHexString =
+            "bf63616c670d64696e7473d39f181a1818ff696f626a546167676564d82ad90539bf63616c6713ff6e6f626a5461676765644172726179d9038f9fd90539bf63616c671903e8ffffff"
+        val referenceHexStringWithBogusTag =
+            "bf63616c670d64696e7473d3d49f181a1818ff696f626a546167676564d82ad90539bf63616c6713ff6e6f626a5461676765644172726179d9038f9fd90539bf63616c671903e8ffffff"
+        val referenceHexStringWithMissingTag =
+            "bf63616c670d64696e74739f181a1818ff696f626a546167676564d82ad90539bf63616c6713ff6e6f626a5461676765644172726179d9038f9fd90539bf63616c671903e8ffffff"
+
+        val superfluousTagged =
+            "bf63616c670d64696e7473d39f181a1818ff696f626a546167676564d82ad90540d90539bf63616c6713ff6e6f626a5461676765644172726179d9038f9fd90539bf63616c671903e8ffffff"
+        val superfluousWrongTaggedTagged =
+            "bf63616c670d64696e7473d39f181a1818ff696f626a546167676564d82bd82ad90540d90539bf63616c6713ff6e6f626a5461676765644172726179d9038f9fd90539bf63616c671903e8ffffff"
+        val untaggedHexString =
+            "bf63616c670d64696e7473d39f181a1818ff696f626a546167676564d82abf63616c6713ff6e6f626a5461676765644172726179d9038f9fbf63616c671903e8ffffff" //no ObjectTags
+        val reference = NestedTagged(
+            alg = 13,
+            ints = intArrayOf(26, 24),
+            objTagged = ClassAsTagged(19),
+            objTaggedArray = listOf((ClassAsTagged(1000)))
+        )
+        val cbor = Cbor {
+            encodeKeyTags = true
+            verifyKeyTags = true
+            verifyValueTags = true
+            useDefiniteLengthEncoding = false
+            verifyObjectTags = true
+            encodeObjectTags = true
+            encodeValueTags = true
+        }
+        assertEquals(referenceHexString, cbor.encodeToHexString(NestedTagged.serializer(), reference))
+        assertEquals(reference, cbor.decodeFromHexString(NestedTagged.serializer(), referenceHexString))
+
+        assertEquals(
+            "More tags found than the 1 tags specified",
+            assertFailsWith(CborDecodingException::class, message = "More tags found than the 1 tags specified") {
+                cbor.decodeFromHexString(NestedTagged.serializer(), referenceHexStringWithBogusTag)
+            }.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.decodeFromHexString(NestedTagged.serializer(), referenceHexStringWithMissingTag)
+            }.message
+        )
+
+
+        assertEquals(
+            reference,
+            Cbor {
+                encodeKeyTags = true
+                encodeValueTags = true
+                encodeObjectTags = true
+                verifyKeyTags = true
+                verifyValueTags = true
+                verifyObjectTags = false
+            }.decodeFromHexString(NestedTagged.serializer(), referenceHexString)
+        )
+
+        assertEquals(
+            reference,
+            Cbor {
+                encodeKeyTags = true
+                encodeValueTags = true
+                encodeObjectTags = true
+                verifyKeyTags = true
+                verifyValueTags = true
+                verifyObjectTags = false
+            }.decodeFromHexString(NestedTagged.serializer(), superfluousTagged)
+        )
+
+        assertEquals(
+            untaggedHexString,
+            Cbor {
+                encodeKeyTags = true
+                encodeValueTags = true
+                verifyKeyTags = true
+                verifyValueTags = true
+                verifyObjectTags = true
+                encodeObjectTags = false
+            }.encodeToHexString(NestedTagged.serializer(), reference)
+        )
+
+
+        assertEquals(
+            reference,
+            Cbor {
+                encodeKeyTags = true
+                encodeValueTags = true
+                encodeObjectTags = true
+                verifyKeyTags = true
+                verifyValueTags = true
+                verifyObjectTags = false
+            }.decodeFromHexString(NestedTagged.serializer(), untaggedHexString)
+        )
+
+        assertContains(
+            assertFailsWith(CborDecodingException::class) {
+                cbor.decodeFromHexString(NestedTagged.serializer(), untaggedHexString)
+            }.message ?: "", "do not match expected tags"
+        )
+
+        assertContains(
+            assertFailsWith(CborDecodingException::class) {
+                Cbor {
+                    encodeKeyTags = true
+                    encodeValueTags = true
+                    encodeObjectTags = true
+                    verifyKeyTags = true
+                    verifyValueTags = true
+                    verifyObjectTags = false
+                }.decodeFromHexString(
+                    NestedTagged.serializer(),
+                    superfluousWrongTaggedTagged
+                )
+            }.message ?: "", "do not start with specified tags"
+        )
+
+
+    }
+
+    @ObjectTags(1337uL)
+    @Serializable
+    data class ClassAsTagged(
+        @SerialName("alg")
+        val alg: Int,
+    )
+
+    @Serializable
+    data class NestedTagged(
+        @SerialName("alg")
+        val alg: Int,
+        @ValueTags(19u)
+        val ints: IntArray,
+
+        @ValueTags(42u)
+        val objTagged: ClassAsTagged,
+        @ValueTags(911u)
+        val objTaggedArray: List<ClassAsTagged>
+    ) {
+        override fun equals(other: Any?): Boolean {
+            if (this === other) return true
+            if (other !is NestedTagged) return false
+
+            if (alg != other.alg) return false
+            if (!ints.contentEquals(other.ints)) return false
+            if (objTagged != other.objTagged) return false
+            if (objTaggedArray != other.objTaggedArray) return false
+
+            return true
+        }
+
+        override fun hashCode(): Int {
+            var result = alg
+            result = 31 * result + ints.contentHashCode()
+            result = 31 * result + objTagged.hashCode()
+            result = 31 * result + objTaggedArray.hashCode()
+            return result
+        }
+    }
+
+
+}
\ No newline at end of file
diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborWriterTest.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborWriterTest.kt
index da7b128..330d4ff 100644
--- a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborWriterTest.kt
+++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborWriterTest.kt
@@ -34,6 +34,25 @@
     }
 
     @Test
+    fun writeComplicatedClassDefLen() {
+        val test = TypesUmbrella(
+            "Hello, world!",
+            42,
+            null,
+            listOf("a", "b"),
+            mapOf(1 to true, 2 to false),
+            Simple("lol"),
+            listOf(Simple("kek")),
+            HexConverter.parseHexBinary("cafe"),
+            HexConverter.parseHexBinary("cafe")
+        )
+        assertEquals(
+            "a9637374726d48656c6c6f2c20776f726c64216169182a686e756c6c61626c65f6646c6973748261616162636d6170a201f502f465696e6e6572a16161636c6f6c6a696e6e6572734c69737481a16161636b656b6a62797465537472696e6742cafe6962797465417272617982383521",
+            Cbor { useDefiniteLengthEncoding = true }.encodeToHexString(TypesUmbrella.serializer(), test)
+        )
+    }
+
+    @Test
     fun writeManyNumbers() {
         val test = NumberTypesUmbrella(
             100500,
@@ -100,26 +119,53 @@
     }
 
     @Test
+    fun testOmitNullForNullableByteString() {
+        /* BF                         # map(*)
+         *    FF                      # primitive(*)
+         */
+        assertEquals(
+            expected = "bfff",
+            actual = Cbor.encodeToHexString(
+                serializer = NullableByteStringDefaultNull.serializer(),
+                value = NullableByteStringDefaultNull(byteString = null)
+            )
+        )
+    }
+
+    @Test
+    fun testOmitNullDefLenForNullableByteString() {
+        /* A0                         # map(0)
+         */
+        assertEquals(
+            expected = "a0",
+            actual = Cbor { useDefiniteLengthEncoding = true }.encodeToHexString(
+                serializer = NullableByteStringDefaultNull.serializer(),
+                value = NullableByteStringDefaultNull(byteString = null)
+            )
+        )
+    }
+
+    @Test
     fun testWriteCustomByteString() {
         assertEquals(
-                expected = "bf617843112233ff",
-                actual = Cbor.encodeToHexString(TypeWithCustomByteString(CustomByteString(0x11, 0x22, 0x33)))
+            expected = "bf617843112233ff",
+            actual = Cbor.encodeToHexString(TypeWithCustomByteString(CustomByteString(0x11, 0x22, 0x33)))
         )
     }
 
     @Test
     fun testWriteNullableCustomByteString() {
         assertEquals(
-                expected = "bf617843112233ff",
-                actual = Cbor.encodeToHexString(TypeWithNullableCustomByteString(CustomByteString(0x11, 0x22, 0x33)))
+            expected = "bf617843112233ff",
+            actual = Cbor.encodeToHexString(TypeWithNullableCustomByteString(CustomByteString(0x11, 0x22, 0x33)))
         )
     }
 
     @Test
     fun testWriteNullCustomByteString() {
         assertEquals(
-                expected = "bf6178f6ff",
-                actual = Cbor.encodeToHexString(TypeWithNullableCustomByteString(null))
+            expected = "bf6178f6ff",
+            actual = Cbor.encodeToHexString(TypeWithNullableCustomByteString(null))
         )
     }
 
diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/SampleClasses.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/SampleClasses.kt
index e4418f4..8feb491 100644
--- a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/SampleClasses.kt
+++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/SampleClasses.kt
@@ -15,15 +15,15 @@
 
 @Serializable
 data class TypesUmbrella(
-        val str: String,
-        val i: Int,
-        val nullable: Double?,
-        val list: List<String>,
-        val map: Map<Int, Boolean>,
-        val inner: Simple,
-        val innersList: List<Simple>,
-        @ByteString val byteString: ByteArray,
-        val byteArray: ByteArray
+    val str: String,
+    val i: Int,
+    val nullable: Double?,
+    val list: List<String>,
+    val map: Map<Int, Boolean>,
+    val inner: Simple,
+    val innersList: List<Simple>,
+    @ByteString val byteString: ByteArray,
+    val byteArray: ByteArray
 ) {
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
@@ -60,12 +60,12 @@
 
 @Serializable
 data class NumberTypesUmbrella(
-        val int: Int,
-        val long: Long,
-        val float: Float,
-        val double: Double,
-        val boolean: Boolean,
-        val char: Char
+    val int: Int,
+    val long: Long,
+    val float: Float,
+    val double: Double,
+    val boolean: Boolean,
+    val char: Char
 )
 
 @Serializable
@@ -91,6 +91,29 @@
     }
 }
 
+@Serializable
+data class NullableByteStringDefaultNull(
+    @ByteString val byteString: ByteArray ? = null
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null || this::class != other::class) return false
+
+        other as NullableByteString
+
+        if (byteString != null) {
+            if (other.byteString == null) return false
+            if (!byteString.contentEquals(other.byteString)) return false
+        } else if (other.byteString != null) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        return byteString?.contentHashCode() ?: 0
+    }
+}
+
 @Serializable(with = CustomByteStringSerializer::class)
 data class CustomByteString(val a: Byte, val b: Byte, val c: Byte)
 
diff --git a/formats/cbor/jvmTest/src/kotlinx/serialization/cbor/CborWriterSpecTest.kt b/formats/cbor/jvmTest/src/kotlinx/serialization/cbor/CborWriterSpecTest.kt
index 364cd67..41ba581 100644
--- a/formats/cbor/jvmTest/src/kotlinx/serialization/cbor/CborWriterSpecTest.kt
+++ b/formats/cbor/jvmTest/src/kotlinx/serialization/cbor/CborWriterSpecTest.kt
@@ -13,9 +13,9 @@
 class CborWriterSpecTest : WordSpec() {
     init {
 
-        fun withEncoder(block: CborEncoder.() -> Unit): String {
+        fun withEncoder(block: ByteArrayOutput.() -> Unit): String {
             val result = ByteArrayOutput()
-            CborEncoder(result).block()
+            result.block()
             return HexConverter.printHexBinary(result.toByteArray()).lowercase()
         }