Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ val documentedSubprojects
"kotlinx-serialization-json-okio",
"kotlinx-serialization-json-io",
"kotlinx-serialization-cbor",
"kotlinx-serialization-cbor-io",
"kotlinx-serialization-properties",
"kotlinx-serialization-hocon",
"kotlinx-serialization-protobuf"
Expand Down
1 change: 1 addition & 0 deletions buildSrc/src/main/kotlin/publishing-conventions.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ val isMultiplatform = name in listOf(
"kotlinx-serialization-json-tests",
"kotlinx-serialization-protobuf",
"kotlinx-serialization-cbor",
"kotlinx-serialization-cbor-io",
"kotlinx-serialization-properties"
)

Expand Down
5 changes: 5 additions & 0 deletions formats/cbor-io/api/kotlinx-serialization-cbor-io.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
public final class kotlinx/serialization/cbor/io/IoStreamsKt {
public static final fun decodeFromSource (Lkotlinx/serialization/cbor/Cbor;Lkotlinx/serialization/DeserializationStrategy;Lkotlinx/io/Source;)Ljava/lang/Object;
public static final fun encodeToSink (Lkotlinx/serialization/cbor/Cbor;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlinx/io/Sink;)V
}

12 changes: 12 additions & 0 deletions formats/cbor-io/api/kotlinx-serialization-cbor-io.klib.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Klib ABI Dump
// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, iosArm64, iosSimulatorArm64, iosX64, js, linuxArm32Hfp, linuxArm64, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, wasmJs, wasmWasi, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64]
// Rendering settings:
// - Signature version: 2
// - Show manifest properties: true
// - Show declarations: true

// Library unique name: <org.jetbrains.kotlinx:kotlinx-serialization-cbor-io>
final fun <#A: kotlin/Any?> (kotlinx.serialization.cbor/Cbor).kotlinx.serialization.cbor.io/decodeFromSource(kotlinx.serialization/DeserializationStrategy<#A>, kotlinx.io/Source): #A // kotlinx.serialization.cbor.io/decodeFromSource|decodeFromSource@kotlinx.serialization.cbor.Cbor(kotlinx.serialization.DeserializationStrategy<0:0>;kotlinx.io.Source){0§<kotlin.Any?>}[0]
final fun <#A: kotlin/Any?> (kotlinx.serialization.cbor/Cbor).kotlinx.serialization.cbor.io/encodeToSink(kotlinx.serialization/SerializationStrategy<#A>, #A, kotlinx.io/Sink) // kotlinx.serialization.cbor.io/encodeToSink|encodeToSink@kotlinx.serialization.cbor.Cbor(kotlinx.serialization.SerializationStrategy<0:0>;0:0;kotlinx.io.Sink){0§<kotlin.Any?>}[0]
final inline fun <#A: reified kotlin/Any?> (kotlinx.serialization.cbor/Cbor).kotlinx.serialization.cbor.io/decodeFromSource(kotlinx.io/Source): #A // kotlinx.serialization.cbor.io/decodeFromSource|decodeFromSource@kotlinx.serialization.cbor.Cbor(kotlinx.io.Source){0§<kotlin.Any?>}[0]
final inline fun <#A: reified kotlin/Any?> (kotlinx.serialization.cbor/Cbor).kotlinx.serialization.cbor.io/encodeToSink(#A, kotlinx.io/Sink) // kotlinx.serialization.cbor.io/encodeToSink|encodeToSink@kotlinx.serialization.cbor.Cbor(0:0;kotlinx.io.Sink){0§<kotlin.Any?>}[0]
41 changes: 41 additions & 0 deletions formats/cbor-io/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
import Java9Modularity.configureJava9ModuleInfo
import Java9Modularity.configureMetadataJarAutomaticModuleName

plugins {
kotlin("multiplatform")
kotlin("plugin.serialization")

id("native-targets-conventions")
id("source-sets-conventions")
}

kotlin {
sourceSets {
configureEach {
languageSettings {
optIn("kotlinx.serialization.internal.CoreFriendModuleApi")
optIn("kotlinx.serialization.cbor.internal.CborFriendModuleApi")
}
}
val commonMain by getting {
dependencies {
api(project(":kotlinx-serialization-core"))
api(project(":kotlinx-serialization-cbor"))
implementation(libs.kotlinx.io)
}
}
}
}

project.configureJava9ModuleInfo()
project.configureMetadataJarAutomaticModuleName()

dokka.dokkaSourceSets.configureEach {
externalDocumentationLinks.register("kotlinx-io") {
url("https://kotlinlang.org/api/kotlinx-io/")
packageListUrl = file("dokka/kotlinx-io.package-list").toURI()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package kotlinx.serialization.cbor.io

import kotlinx.io.*
import kotlinx.serialization.*
import kotlinx.serialization.cbor.*
import kotlinx.serialization.cbor.io.internal.*

/**
* Serializes the [value] with [serializer] into a [sink] using CBOR format.
*
* @throws [SerializationException] if the given value cannot be serialized to CBOR.
* @throws [kotlinx.io.IOException] If an I/O error occurs and sink can't be written to.
*/
@ExperimentalSerializationApi
public fun <T> Cbor.encodeToSink(serializer: SerializationStrategy<T>, value: T, sink: Sink) {
encodeToOutput(serializer, value, IoStreamOutput(sink))
}

/**
* Serializes given [value] to a [sink] using CBOR format and serializer retrieved from the reified type parameter.
*
* @throws [SerializationException] if the given value cannot be serialized to CBOR.
* @throws [kotlinx.io.IOException] If an I/O error occurs and sink can't be written to.
*/
@ExperimentalSerializationApi
public inline fun <reified T> Cbor.encodeToSink(
value: T,
sink: Sink
): Unit = encodeToSink(serializersModule.serializer(), value, sink)

/**
* Deserializes CBOR from [source] to a value of type [T] using [deserializer].
*
* Note that this functions expects that exactly one object would be present in the source
* and throws an exception if there are any dangling bytes after an object.
*
* @throws [SerializationException] if the given CBOR input cannot be deserialized to the value of type [T].
* @throws [kotlinx.io.IOException] If an I/O error occurs and source can't be read from.
*/
@ExperimentalSerializationApi
public fun <T> Cbor.decodeFromSource(deserializer: DeserializationStrategy<T>, source: Source): T =
decodeFromInput(deserializer, IoStreamInput(source))

/**
* Deserializes CBOR from [source] to a value of type [T] using deserializer retrieved from the reified type parameter.
*
* Note that this functions expects that exactly one object would be present in the stream
* and throws an exception if there are any dangling bytes after an object.
*
* @throws [SerializationException] if the given CBOR input cannot be deserialized to the value of type [T].
* @throws [kotlinx.io.IOException] If an I/O error occurs and source can't be read from.
*/
@ExperimentalSerializationApi
public inline fun <reified T> Cbor.decodeFromSource(source: Source): T =
decodeFromSource(serializersModule.serializer(), source)
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2017-2026 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.serialization.cbor.io.internal

import kotlinx.io.*
import kotlinx.serialization.cbor.internal.*

internal class IoStreamOutput(private val sink: Sink) : Output {

override fun write(buffer: ByteArray, offset: Int, count: Int) {
sink.write(buffer, startIndex = offset, endIndex = offset + count)
}

override fun write(byteValue: Byte) {
sink.writeByte(byteValue)
}
}

internal class IoStreamInput(private val source: Source): Input {
override val availableBytes: Int
get() = source.peek().readByteArray().size
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using peek and reading whole input to the memory is a huge potential performance problem. exhausted() should be used instead. Yes, it returns Boolean, so calls of availableBytes should also be adapted.


override fun read(): Int =
try {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using try-catch on the hot path is also a performance problem. read() seems to be called only from methods reading byte/int/long/etc. These methods should be adapted and use corresponding Sink methods instead (readByte()/readInt()/etc). It will also (probably) automatically give us IOException without the need to use availableBytes — see above.

source.readByte().toInt()
} catch (_: EOFException) {
return -1
}

override fun read(b: ByteArray, offset: Int, length: Int): Int =
source.readAtMostTo(b, startIndex = offset, endIndex = offset + length)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semantic is different: readAtMostTo can read fewer bytes than length in order not to block, while Input.read expects all the bytes. In this case, a reading loop should be implemented. @fzhinkin I do not remember why we don't have readExactTo in Source?

We have Source.readTo(sink: ByteArray, startIndex: Int = 0, endIndex: Int = sink.size). Use it instead. Return value of read(b, offset, len) is not needed anyway.


override fun skip(length: Int) {
source.skip(length.toLong())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.serialization.cbor.io

import kotlinx.io.*
import kotlinx.serialization.*
import kotlinx.serialization.cbor.*
import kotlin.test.*

class IoTests {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preferably, the whole test suite should be adapted to work with IO streams; see how it is done in JsonTestBase with JsonTestingMode.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, a pair of basic benchmarks (see CborBaseline.kt) would be nice.


@Serializable
data class Simple(val i: Int)

@Test
fun testEncodingDefinite() {
val value = Simple(42)
val buffer = Buffer()
Cbor { useDefiniteLengthEncoding = true }.encodeToSink(value, buffer)
assertEquals(expected = "a16169182a", actual = buffer.readByteArray().toHexString())
}

@Test
fun testEncodingIndefinite() {
val buffer = Buffer()
Cbor { useDefiniteLengthEncoding = false }.encodeToSink(Simple(42), buffer)
assertEquals(expected = "bf6169182aff", actual = buffer.readByteArray().toHexString())
}

@Test
fun testDecoding() {
val buffer = Buffer()
buffer.write("a16169182a".hexToByteArray())
val decoded = Cbor.decodeFromSource<Simple>(buffer)
assertEquals(expected = Simple(42), actual = decoded)

assertTrue(buffer.exhausted())
}

@Test
fun testDecodingFailsWithUnprocessedBytes() {
val buffer = Buffer()
buffer.write("bf6169182aff00".hexToByteArray())
assertFailsWith<SerializationException> { Cbor.decodeFromSource<Simple>(buffer) }
}
}
58 changes: 58 additions & 0 deletions formats/cbor/api/kotlinx-serialization-cbor.api
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ public abstract class kotlinx/serialization/cbor/Cbor : kotlinx/serialization/Bi
public static final field Default Lkotlinx/serialization/cbor/Cbor$Default;
public synthetic fun <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 final fun decodeFromInput (Lkotlinx/serialization/DeserializationStrategy;Lkotlinx/serialization/cbor/internal/Input;)Ljava/lang/Object;
public fun encodeToByteArray (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)[B
public final fun encodeToOutput (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlinx/serialization/cbor/internal/Output;)V
public final fun getConfiguration ()Lkotlinx/serialization/cbor/CborConfiguration;
public fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule;
}
Expand Down Expand Up @@ -149,3 +151,59 @@ public final synthetic class kotlinx/serialization/cbor/ValueTags$Impl : kotlinx
public final synthetic fun tags ()[J
}

public abstract class kotlinx/serialization/cbor/internal/CborWriter : kotlinx/serialization/encoding/AbstractEncoder, kotlinx/serialization/cbor/CborEncoder {
public synthetic fun <init> (Lkotlinx/serialization/cbor/Cbor;Lkotlinx/serialization/cbor/internal/Output;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun encodeBoolean (Z)V
public fun encodeByte (B)V
public fun encodeChar (C)V
public fun encodeDouble (D)V
public fun encodeElement (Lkotlinx/serialization/descriptors/SerialDescriptor;I)Z
public fun encodeEnum (Lkotlinx/serialization/descriptors/SerialDescriptor;I)V
public fun encodeFloat (F)V
public fun encodeInt (I)V
public fun encodeLong (J)V
public fun encodeNull ()V
public fun encodeSerializableValue (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V
public fun encodeShort (S)V
public fun encodeString (Ljava/lang/String;)V
public fun getCbor ()Lkotlinx/serialization/cbor/Cbor;
protected abstract fun getDestination ()Lkotlinx/serialization/cbor/internal/Output;
protected final fun getEncodeByteArrayAsByteString ()Z
protected final fun getOutput ()Lkotlinx/serialization/cbor/internal/Output;
public fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule;
protected abstract fun incrementChildren ()V
protected final fun isClass ()Z
protected final fun setClass (Z)V
protected final fun setEncodeByteArrayAsByteString (Z)V
public fun shouldEncodeElementDefault (Lkotlinx/serialization/descriptors/SerialDescriptor;I)Z
}

public final class kotlinx/serialization/cbor/internal/DefiniteLengthCborWriter : kotlinx/serialization/cbor/internal/CborWriter {
public fun <init> (Lkotlinx/serialization/cbor/Cbor;Lkotlinx/serialization/cbor/internal/Output;)V
public fun beginStructure (Lkotlinx/serialization/descriptors/SerialDescriptor;)Lkotlinx/serialization/encoding/CompositeEncoder;
public fun endStructure (Lkotlinx/serialization/descriptors/SerialDescriptor;)V
}

public final class kotlinx/serialization/cbor/internal/IndefiniteLengthCborWriter : kotlinx/serialization/cbor/internal/CborWriter {
public fun <init> (Lkotlinx/serialization/cbor/Cbor;Lkotlinx/serialization/cbor/internal/Output;)V
public fun beginStructure (Lkotlinx/serialization/descriptors/SerialDescriptor;)Lkotlinx/serialization/encoding/CompositeEncoder;
public fun endStructure (Lkotlinx/serialization/descriptors/SerialDescriptor;)V
}

public abstract interface class kotlinx/serialization/cbor/internal/Input {
public abstract fun getAvailableBytes ()I
public abstract fun read ()I
public abstract fun read ([BII)I
public abstract fun skip (I)V
}

public abstract interface class kotlinx/serialization/cbor/internal/Output {
public abstract fun write (B)V
public abstract fun write ([BII)V
public static synthetic fun write$default (Lkotlinx/serialization/cbor/internal/Output;[BIIILjava/lang/Object;)V
}

public final class kotlinx/serialization/cbor/internal/Output$DefaultImpls {
public static synthetic fun write$default (Lkotlinx/serialization/cbor/internal/Output;[BIIILjava/lang/Object;)V
}

Loading