Skip to content

Commit 998f01e

Browse files
committed
tree encoding half-done
1 parent 6984f69 commit 998f01e

File tree

5 files changed

+197
-14
lines changed

5 files changed

+197
-14
lines changed

formats/cbor/commonMain/src/kotlinx/serialization/cbor/Cbor.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,19 @@ public sealed class Cbor(
8888
val reader = CborReader(this, CborParser(stream, configuration.verifyObjectTags))
8989
return reader.decodeSerializableValue(deserializer)
9090
}
91+
92+
93+
public fun <T> encodeToCbor(serializer: SerializationStrategy<T>, value: T): CborElement {
94+
val writer = StructuredCborWriter(this)
95+
writer.encodeSerializableValue(serializer, value)
96+
return writer.finalize()
97+
}
9198
}
9299

100+
@ExperimentalSerializationApi
101+
public inline fun <reified T> Cbor.encodeToCbor(value: T): CborElement =
102+
encodeToCbor(serializersModule.serializer(), value)
103+
93104
@OptIn(ExperimentalSerializationApi::class)
94105
private class CborImpl(
95106
configuration: CborConfiguration,

formats/cbor/commonMain/src/kotlinx/serialization/cbor/CborElement.kt

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,19 @@ public sealed class CborElement(
2828
* See [RFC 8949 3.4. Tagging of Items](https://datatracker.ietf.org/doc/html/rfc8949#name-tagging-of-items).
2929
*/
3030
@OptIn(ExperimentalUnsignedTypes::class)
31-
public val tags: ULongArray = ulongArrayOf()
32-
)
31+
tags: ULongArray = ulongArrayOf()
32+
33+
) {
34+
/**
35+
* CBOR tags associated with this element.
36+
* Tags are optional semantic tagging of other major types (major type 6).
37+
* See [RFC 8949 3.4. Tagging of Items](https://datatracker.ietf.org/doc/html/rfc8949#name-tagging-of-items).
38+
*/
39+
@OptIn(ExperimentalUnsignedTypes::class)
40+
public var tags: ULongArray = tags
41+
internal set
42+
43+
}
3344

3445
/**
3546
* Class representing CBOR primitive value.
@@ -73,6 +84,11 @@ public class CborPositiveInt(
7384
override fun hashCode(): Int = value.hashCode() * 31 + tags.contentHashCode()
7485
}
7586

87+
public fun CborInt(
88+
value: Long,
89+
tags: ULongArray = ulongArrayOf()
90+
): CborPrimitive = if (value >= 0) CborPositiveInt(value.toULong(), tags) else CborNegativeInt(value, tags)
91+
7692
/**
7793
* Class representing CBOR floating point value (major type 7).
7894
*/
@@ -147,7 +163,7 @@ public class CborByteString(
147163
* Class representing CBOR `null` value
148164
*/
149165
@Serializable(with = CborNullSerializer::class)
150-
public class CborNull(tags: ULongArray=ulongArrayOf()) : CborPrimitive(tags) {
166+
public class CborNull(tags: ULongArray = ulongArrayOf()) : CborPrimitive(tags) {
151167
// Note: CborNull is an object, so it cannot have constructor parameters for tags
152168
// If tags are needed for null values, this would need to be changed to a class
153169
override fun equals(other: Any?): Boolean {
@@ -172,12 +188,12 @@ public class CborMap(
172188
private val content: Map<CborElement, CborElement>,
173189
tags: ULongArray = ulongArrayOf()
174190
) : CborElement(tags), Map<CborElement, CborElement> by content {
175-
191+
176192
public override fun equals(other: Any?): Boolean =
177193
other is CborMap && other.content == content && other.tags.contentEquals(tags)
178194

179195
public override fun hashCode(): Int = content.hashCode() * 31 + tags.contentHashCode()
180-
196+
181197
public override fun toString(): String = content.toString()
182198
}
183199

@@ -192,11 +208,11 @@ public class CborList(
192208
private val content: List<CborElement>,
193209
tags: ULongArray = ulongArrayOf()
194210
) : CborElement(tags), List<CborElement> by content {
195-
211+
196212
public override fun equals(other: Any?): Boolean =
197213
other is CborList && other.content == content && other.tags.contentEquals(tags)
198214

199215
public override fun hashCode(): Int = content.hashCode() * 31 + tags.contentHashCode()
200-
216+
201217
public override fun toString(): String = content.toString()
202218
}

formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/CborElementSerializers.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ public object CborBooleanSerializer : KSerializer<CborBoolean> {
189189

190190
/**
191191
* Serializer object providing [SerializationStrategy] and [DeserializationStrategy] for [CborByteString].
192-
* It can only be used by with [Cbor] format an its input ([CborDecoder] and [CborEncoder]).
192+
* It can only be used by with [Cbor] format and its input ([CborDecoder] and [CborEncoder]).
193193
*/
194194
public object CborByteStringSerializer : KSerializer<CborByteString> {
195195
override val descriptor: SerialDescriptor =
@@ -211,7 +211,7 @@ public object CborByteStringSerializer : KSerializer<CborByteString> {
211211

212212
/**
213213
* Serializer object providing [SerializationStrategy] and [DeserializationStrategy] for [CborMap].
214-
* It can only be used by with [Cbor] format an its input ([CborDecoder] and [CborEncoder]).
214+
* It can only be used by with [Cbor] format and its input ([CborDecoder] and [CborEncoder]).
215215
*/
216216
public object CborMapSerializer : KSerializer<CborMap> {
217217
private object CborMapDescriptor :

formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Encoder.kt

Lines changed: 158 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,9 @@ private fun Stack.peek() = last()
2626
// Split implementation to optimize base case
2727
internal sealed class CborWriter(
2828
override val cbor: Cbor,
29-
protected val output: ByteArrayOutput,
3029
) : AbstractEncoder(), CborEncoder {
3130

32-
internal fun encodeByteString(byteArray: ByteArray) {
31+
internal open fun encodeByteString(byteArray: ByteArray) {
3332
getDestination().encodeByteString(byteArray)
3433
}
3534

@@ -155,8 +154,8 @@ internal sealed class CborWriter(
155154

156155

157156
// optimized indefinite length encoder
158-
internal class IndefiniteLengthCborWriter(cbor: Cbor, output: ByteArrayOutput) : CborWriter(
159-
cbor, output
157+
internal class IndefiniteLengthCborWriter(cbor: Cbor, private val output: ByteArrayOutput) : CborWriter(
158+
cbor
160159
) {
161160

162161
override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder {
@@ -187,8 +186,162 @@ internal class IndefiniteLengthCborWriter(cbor: Cbor, output: ByteArrayOutput) :
187186

188187
}
189188

189+
// optimized indefinite length encoder
190+
internal class StructuredCborWriter(cbor: Cbor) : CborWriter(
191+
cbor
192+
) {
193+
194+
sealed class CborContainer(tags: ULongArray, elements: MutableList<CborElement>) {
195+
var elements = elements
196+
private set
197+
198+
var tags = tags
199+
internal set
200+
201+
202+
fun add(element: CborElement) = elements.add(element)
203+
class Map(tags: ULongArray, elements: MutableList<CborElement> = mutableListOf()) :
204+
CborContainer(tags, elements) {
205+
}
206+
207+
class List(tags: ULongArray, elements: MutableList<CborElement> = mutableListOf()) :
208+
CborContainer(tags, elements) {
209+
}
210+
211+
class Primitive(tags: ULongArray) : CborContainer(tags, elements = mutableListOf()) {
212+
213+
}
214+
215+
fun finalize() = when (this) {
216+
is List -> CborList(content = elements, tags = tags)
217+
is Map -> CborMap(
218+
content = if (elements.isNotEmpty()) IntRange(0, elements.size / 2 - 1).associate {
219+
elements[it * 2] to elements[it * 2 + 1]
220+
} else mapOf(),
221+
tags = tags
222+
)
223+
224+
is Primitive -> elements.first().also { it.tags = tags }
225+
226+
}
227+
}
228+
229+
private val stack = ArrayDeque<CborContainer>()
230+
private var currentElement: CborContainer? = null
231+
232+
fun finalize() =currentElement!!.finalize()
233+
234+
override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder {
235+
val tags = descriptor.getObjectTags() ?: ulongArrayOf()
236+
val element = if (descriptor.hasArrayTag()) {
237+
CborContainer.List(tags)
238+
} else {
239+
when (descriptor.kind) {
240+
StructureKind.LIST, is PolymorphicKind -> CborContainer.List(tags)
241+
is StructureKind.MAP -> CborContainer.Map(tags)
242+
else -> CborContainer.Map(tags)
243+
}
244+
}
245+
currentElement?.let { stack.add(it) }
246+
currentElement = element
247+
return this
248+
}
249+
250+
override fun endStructure(descriptor: SerialDescriptor) {
251+
val finalized = currentElement!!.finalize()
252+
if (stack.isNotEmpty()) {
253+
currentElement = stack.removeLast()
254+
currentElement!!.add(finalized)
255+
}
256+
}
257+
258+
override fun getDestination() = TODO()
259+
260+
261+
override fun incrementChildren() {/*NOOP*/
262+
}
263+
264+
265+
override fun encodeElement(descriptor: SerialDescriptor, index: Int): Boolean {
266+
//TODO check if cborelement and be done
267+
val name = descriptor.getElementName(index)
268+
if (!descriptor.hasArrayTag()) {
269+
val keyTags = descriptor.getKeyTags(index)
270+
271+
if ((descriptor.kind !is StructureKind.LIST) && (descriptor.kind !is StructureKind.MAP) && (descriptor.kind !is PolymorphicKind)) {
272+
//indices are put into the name field. we don't want to write those, as it would result in double writes
273+
val cborLabel = descriptor.getCborLabel(index)
274+
if (cbor.configuration.preferCborLabelsOverNames && cborLabel != null) {
275+
currentElement!!.add(
276+
CborInt(cborLabel, keyTags ?: ulongArrayOf())
277+
)
278+
} else {
279+
currentElement!!.add(CborString(name, keyTags ?: ulongArrayOf()))
280+
}
281+
}
282+
}
283+
284+
if (cbor.configuration.encodeValueTags) {
285+
descriptor.getValueTags(index)?.let { valueTags ->
286+
currentElement!!.tags += valueTags
287+
}
288+
}
289+
return true
290+
}
291+
292+
293+
override fun encodeBoolean(value: Boolean) {
294+
currentElement!!.add(CborBoolean(value))
295+
}
296+
297+
override fun encodeByte(value: Byte) {
298+
currentElement!!.add(CborInt(value.toLong()))
299+
}
300+
301+
override fun encodeChar(value: Char) {
302+
currentElement!!.add(CborInt(value.code.toLong()))
303+
}
304+
305+
override fun encodeDouble(value: Double) {
306+
currentElement!!.add(CborDouble(value))
307+
}
308+
309+
override fun encodeFloat(value: Float) {
310+
currentElement!!.add(CborDouble(value.toDouble()))
311+
}
312+
313+
override fun encodeInt(value: Int) {
314+
currentElement!!.add(CborInt(value.toLong()))
315+
}
316+
317+
override fun encodeLong(value: Long) {
318+
currentElement!!.add(CborInt(value))
319+
}
320+
321+
override fun encodeShort(value: Short) {
322+
currentElement!!.add(CborInt(value.toLong()))
323+
}
324+
325+
override fun encodeString(value: String) {
326+
currentElement!!.add(CborString(value))
327+
}
328+
329+
override fun encodeByteString(byteArray: ByteArray) {
330+
currentElement!!.add(CborByteString(byteArray))
331+
}
332+
333+
override fun encodeNull() {
334+
currentElement!!.add(CborNull())
335+
}
336+
337+
override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) {
338+
currentElement!!.add(CborString(enumDescriptor.getElementName(index)))
339+
}
340+
341+
}
342+
190343
//optimized definite length encoder
191-
internal class DefiniteLengthCborWriter(cbor: Cbor, output: ByteArrayOutput) : CborWriter(cbor, output) {
344+
internal class DefiniteLengthCborWriter(cbor: Cbor, output: ByteArrayOutput) : CborWriter(cbor) {
192345

193346
private val structureStack = Stack(Data(output, -1))
194347
override fun getDestination(): ByteArrayOutput =

formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborWriterTest.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package kotlinx.serialization.cbor
66

77
import kotlinx.serialization.*
8+
import kotlinx.serialization.cbor.CborSkipTagAndEmptyTest.DataClass
89
import kotlin.test.*
910

1011

@@ -31,6 +32,8 @@ class CbrWriterTest {
3132
"bf637374726d48656c6c6f2c20776f726c64216169182a686e756c6c61626c65f6646c6973749f61616162ff636d6170bf01f502f4ff65696e6e6572bf6161636c6f6cff6a696e6e6572734c6973749fbf6161636b656bffff6a62797465537472696e6742cafe696279746541727261799f383521ffff",
3233
Cbor.encodeToHexString(TypesUmbrella.serializer(), test)
3334
)
35+
val struct= Cbor.encodeToCbor(test)
36+
println(struct)
3437
}
3538

3639
@Test

0 commit comments

Comments
 (0)