All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.kotlinx.serialization.cbor.Cbor.kt Maven / Gradle / Ivy

/*
 * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
 */

package kotlinx.serialization.cbor

import kotlinx.serialization.*
import kotlinx.serialization.cbor.internal.*
import kotlinx.serialization.modules.*

/**
 * Implements [encoding][encodeToByteArray] and [decoding][decodeFromByteArray] classes to/from bytes
 * using [CBOR](https://tools.ietf.org/html/rfc7049) specification.
 * It is typically used by constructing an application-specific instance, with configured behaviour, and,
 * if necessary, registered custom serializers (in [SerializersModule] provided by [serializersModule] constructor parameter).
 *
 * ### Known caveats and limitations:
 * 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.
 */
@ExperimentalSerializationApi
public sealed class Cbor(
    public val configuration: CborConfiguration,
    override val serializersModule: SerializersModule
) : BinaryFormat {

    /**
     * The default instance of [Cbor]. Neither writes nor verifies tags. Uses indefinite length encoding by default.
     */
    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  encodeToByteArray(serializer: SerializationStrategy, value: T): ByteArray {
        val output = ByteArrayOutput()
        val dumper = if (configuration.useDefiniteLengthEncoding) DefiniteLengthCborWriter(
            this,
            output
        ) else IndefiniteLengthCborWriter(
            this,
            output
        )
        dumper.encodeSerializableValue(serializer, value)

        return output.toByteArray()

    }

    override fun  decodeFromByteArray(deserializer: DeserializationStrategy, bytes: ByteArray): T {
        val stream = ByteArrayInput(bytes)
        val reader = CborReader(this, CborParser(stream, configuration.verifyObjectTags))
        return reader.decodeSerializableValue(deserializer)
    }
}

@OptIn(ExperimentalSerializationApi::class)
private class CborImpl(
    configuration: CborConfiguration,
    serializersModule: SerializersModule
) :
    Cbor(
        configuration,
        serializersModule
    )

/**
 * Creates an instance of [Cbor] configured from the optionally given [Cbor instance][from]
 * and adjusted with [builderAction].
 */
@ExperimentalSerializationApi
public fun Cbor(from: Cbor = Cbor, builderAction: CborBuilder.() -> Unit): Cbor {
    val builder = CborBuilder(from)
    builder.builderAction()
    return CborImpl(CborConfiguration(
        builder.encodeDefaults,
        builder.ignoreUnknownKeys,
        builder.encodeKeyTags,
        builder.encodeValueTags,
        builder.encodeObjectTags,
        builder.verifyKeyTags,
        builder.verifyValueTags,
        builder.verifyObjectTags,
        builder.useDefiniteLengthEncoding,
        builder.preferCborLabelsOverNames,
        builder.alwaysUseByteString),
        builder.serializersModule
    )
}

/**
 * Builder of the [Cbor] instance provided by `Cbor` factory function.
 */
@ExperimentalSerializationApi
public class CborBuilder internal constructor(cbor: Cbor) {

    /**
     * Specifies whether default values of Kotlin properties should be encoded.
     */
    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.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.
     */
    public var serializersModule: SerializersModule = cbor.serializersModule
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy