From 3658c5f903dc7b8c91327754b5662e3c29eeb4b9 Mon Sep 17 00:00:00 2001 From: Rasmus Ros Date: Thu, 21 May 2026 14:29:14 +0200 Subject: [PATCH 1/6] chore: bump kbuild to 1.2.0 and kotlin serialization plugin to 2.3.20 --- build.gradle.kts | 12 +- .../kotlin/com/eignex/kencode/Annotations.kt | 9 +- .../kotlin/com/eignex/kencode/Base64.kt | 6 +- .../kotlin/com/eignex/kencode/Base85.kt | 6 +- .../kotlin/com/eignex/kencode/BaseRadix.kt | 25 ++- .../kotlin/com/eignex/kencode/ByteEncoding.kt | 6 +- .../kotlin/com/eignex/kencode/Crc.kt | 8 +- .../com/eignex/kencode/EncodedFormat.kt | 20 +-- .../com/eignex/kencode/PackedContext.kt | 24 +-- .../com/eignex/kencode/PackedDecoder.kt | 153 +++++++---------- .../com/eignex/kencode/PackedEncoder.kt | 107 ++++-------- .../kotlin/com/eignex/kencode/PackedFormat.kt | 20 +-- .../kotlin/com/eignex/kencode/PackedUtils.kt | 7 +- .../com/eignex/kencode/PayloadTransform.kt | 13 +- .../kotlin/com/eignex/kencode/Base64Test.kt | 20 +-- .../kotlin/com/eignex/kencode/Base85Test.kt | 9 +- .../com/eignex/kencode/BaseRadixTest.kt | 28 ++-- .../kotlin/com/eignex/kencode/CrcTest.kt | 27 ++- .../com/eignex/kencode/EncodedFormatTest.kt | 8 +- .../com/eignex/kencode/PackedContextTest.kt | 30 ++-- .../com/eignex/kencode/PackedFormatTest.kt | 154 +++++++++--------- .../com/eignex/kencode/PackedUtilsTest.kt | 28 ++-- .../kotlin/com/eignex/kencode/TestClasses.kt | 109 ++++--------- .../com/eignex/kencode/ReadmeExamples.kt | 11 +- 24 files changed, 320 insertions(+), 520 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 1de69c9..28945d0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,6 @@ plugins { - id("com.eignex.kmp") version "1.1.5" - kotlin("plugin.serialization") version "2.3.0" + id("com.eignex.kmp") version "1.2.0" + kotlin("plugin.serialization") version "2.3.20" } eignexPublish { @@ -19,13 +19,13 @@ kotlin { sourceSets { commonMain.dependencies { - compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-core:1.10.0") - compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.10.0") + compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-core") + compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-protobuf") implementation("com.ionspin.kotlin:bignum:0.3.10") } commonTest.dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.10.0") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.10.0") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-core") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf") } jvmTest.dependencies { implementation("org.bouncycastle:bcprov-jdk18on:1.83") diff --git a/src/commonMain/kotlin/com/eignex/kencode/Annotations.kt b/src/commonMain/kotlin/com/eignex/kencode/Annotations.kt index 9f756f0..482facb 100644 --- a/src/commonMain/kotlin/com/eignex/kencode/Annotations.kt +++ b/src/commonMain/kotlin/com/eignex/kencode/Annotations.kt @@ -20,7 +20,7 @@ enum class IntPacking { SIGNED, /** Fixed-width. 4 bytes for `Int`, 8 bytes for `Long`. */ - FIXED + FIXED, } /** @@ -32,7 +32,7 @@ enum class IntPacking { @Retention(AnnotationRetention.SOURCE) annotation class PackedType( /** The integer encoding strategy to use for the annotated field. */ - val type: IntPacking + val type: IntPacking, ) private fun KxProtoIntegerType.toIntPacking(): IntPacking = when (this) { @@ -41,10 +41,7 @@ private fun KxProtoIntegerType.toIntPacking(): IntPacking = when (this) { KxProtoIntegerType.FIXED -> IntPacking.FIXED } -internal fun resolveIntEncoding( - anns: List, - config: PackedConfiguration -): IntPacking { +internal fun resolveIntEncoding(anns: List, config: PackedConfiguration): IntPacking { anns.filterIsInstance().firstOrNull()?.let { return it.type } try { anns.filterIsInstance().firstOrNull() diff --git a/src/commonMain/kotlin/com/eignex/kencode/Base64.kt b/src/commonMain/kotlin/com/eignex/kencode/Base64.kt index 9f93f05..3717ad1 100644 --- a/src/commonMain/kotlin/com/eignex/kencode/Base64.kt +++ b/src/commonMain/kotlin/com/eignex/kencode/Base64.kt @@ -32,11 +32,7 @@ open class Base64(val alphabet: CharArray) : ByteEncoding { this['='.code] = 0 } - override fun encode( - input: ByteArray, - offset: Int, - length: Int - ): String { + override fun encode(input: ByteArray, offset: Int, length: Int): String { require(offset >= 0 && length >= 0 && offset + length <= input.size) { "Invalid offset/length for input of size ${input.size}" } diff --git a/src/commonMain/kotlin/com/eignex/kencode/Base85.kt b/src/commonMain/kotlin/com/eignex/kencode/Base85.kt index 61cb97b..f0fb306 100644 --- a/src/commonMain/kotlin/com/eignex/kencode/Base85.kt +++ b/src/commonMain/kotlin/com/eignex/kencode/Base85.kt @@ -23,11 +23,7 @@ open class Base85(private val alphabet: CharArray) : ByteEncoding { } } - override fun encode( - input: ByteArray, - offset: Int, - length: Int - ): String { + override fun encode(input: ByteArray, offset: Int, length: Int): String { require(offset >= 0 && length >= 0 && offset + length <= input.size) { "Invalid offset/length for input of size ${input.size}" } diff --git a/src/commonMain/kotlin/com/eignex/kencode/BaseRadix.kt b/src/commonMain/kotlin/com/eignex/kencode/BaseRadix.kt index 441f6a4..92fa851 100644 --- a/src/commonMain/kotlin/com/eignex/kencode/BaseRadix.kt +++ b/src/commonMain/kotlin/com/eignex/kencode/BaseRadix.kt @@ -57,10 +57,7 @@ class CharAlphabet(private val chars: String) : Alphabet { * Defaults to U+0020 – U+D7FF (55,264 characters), the largest BMP range * that avoids surrogate code points. */ -class UnicodeRangeAlphabet( - private val start: Int = 0x0020, - override val size: Int = 0xD800 - 0x0020 -) : Alphabet { +class UnicodeRangeAlphabet(private val start: Int = 0x0020, override val size: Int = 0xD800 - 0x0020) : Alphabet { init { require(size > 1) { "Alphabet must contain at least 2 characters." } require(start >= 0) { "Unicode range start must be non-negative." } @@ -84,8 +81,7 @@ class UnicodeRangeAlphabet( * @property blockSize Number of input bytes processed per block; larger blocks pack more * tightly but cost more BigInteger arithmetic. */ -open class BaseRadix(private val alphabet: Alphabet, val blockSize: Int = 32) : - ByteEncoding { +open class BaseRadix(private val alphabet: Alphabet, val blockSize: Int = 32) : ByteEncoding { constructor(chars: String, blockSize: Int = 32) : this(CharAlphabet(chars), blockSize) @@ -101,7 +97,7 @@ open class BaseRadix(private val alphabet: Alphabet, val blockSize: Int = 32) : // Full block length must exceed any partial block's so the decoder can split unambiguously. private val massiveFullBlockLen: Int = maxOf( ceil((blockSize * 8) / logBase).toInt(), - ceil(((blockSize - 1) * 8) / logBase).toInt() + 1 + ceil(((blockSize - 1) * 8) / logBase).toInt() + 1, ) private val lengths: IntArray = IntArray(blockSize) { blockIndex -> @@ -183,7 +179,7 @@ open class BaseRadix(private val alphabet: Alphabet, val blockSize: Int = 32) : inPos: Int = 0, inLen: Int = input.size, output: StringBuilder = StringBuilder(lengths.last()), - outLen: Int = lengths[inLen - 1] + outLen: Int = lengths[inLen - 1], ) { var n = bigIntOf(input, inPos, inLen) val startPos = output.length @@ -202,7 +198,7 @@ open class BaseRadix(private val alphabet: Alphabet, val blockSize: Int = 32) : inLen: Int = input.length, output: ByteArray = ByteArray(invLengths[inLen - 1]), outPos: Int = 0, - outLen: Int = invLengths[inLen - 1] + outLen: Int = invLengths[inLen - 1], ) { var n = bigZero for (i in inPos until (inPos + inLen)) { @@ -252,11 +248,10 @@ open class BaseRadix(private val alphabet: Alphabet, val blockSize: Int = 32) : } } - private fun bigIntOf(input: ByteArray, inPos: Int, inLen: Int): BigInteger = - BigInteger.fromByteArray( - if (inPos == 0 && inLen == input.size) input else input.sliceArray(inPos until inPos + inLen), - Sign.POSITIVE - ) + private fun bigIntOf(input: ByteArray, inPos: Int, inLen: Int): BigInteger = BigInteger.fromByteArray( + if (inPos == 0 && inLen == input.size) input else input.sliceArray(inPos until inPos + inLen), + Sign.POSITIVE, + ) private fun stripSignByte(bytes: ByteArray): ByteArray = if (bytes.size > 1 && bytes[0] == 0.toByte()) bytes.sliceArray(1 until bytes.size) else bytes @@ -306,7 +301,7 @@ open class BaseRadix(private val alphabet: Alphabet, val blockSize: Int = 32) : val bytes = n.toByteArray() val actualBytes = if (bytes.size > 1 && bytes[0] == 0.toByte()) { bytes.sliceArray( - 1 until bytes.size + 1 until bytes.size, ) } else { bytes diff --git a/src/commonMain/kotlin/com/eignex/kencode/ByteEncoding.kt b/src/commonMain/kotlin/com/eignex/kencode/ByteEncoding.kt index dd6596a..b4a3f75 100644 --- a/src/commonMain/kotlin/com/eignex/kencode/ByteEncoding.kt +++ b/src/commonMain/kotlin/com/eignex/kencode/ByteEncoding.kt @@ -8,11 +8,7 @@ interface ByteEncoding { /** * Encode the given byte range into a text representation. */ - fun encode( - input: ByteArray, - offset: Int = 0, - length: Int = input.size - offset - ): String + fun encode(input: ByteArray, offset: Int = 0, length: Int = input.size - offset): String /** * Decode an encoded string back into the original bytes. diff --git a/src/commonMain/kotlin/com/eignex/kencode/Crc.kt b/src/commonMain/kotlin/com/eignex/kencode/Crc.kt index bc9268c..03ee14c 100644 --- a/src/commonMain/kotlin/com/eignex/kencode/Crc.kt +++ b/src/commonMain/kotlin/com/eignex/kencode/Crc.kt @@ -9,7 +9,7 @@ private class CrcEngine( private val refin: Boolean, private val refout: Boolean, xorOut: Int, - private val width: Int + private val width: Int, ) { private val mask: Long = if (width == 32) 0xFFFFFFFFL else (1L shl width) - 1L @@ -55,7 +55,7 @@ open class Crc8( init: Int = 0x00, refin: Boolean = false, refout: Boolean = false, - xorOut: Int = 0x00 + xorOut: Int = 0x00, ) : Checksum { /** Default CRC-8/SMBUS instance. */ companion object Default : Crc8() @@ -73,7 +73,7 @@ open class Crc16( init: Int = 0xFFFF, refin: Boolean = true, refout: Boolean = true, - xorOut: Int = 0xFFFF + xorOut: Int = 0xFFFF, ) : Checksum { /** Default CRC-16/X-25 instance. */ companion object Default : Crc16() @@ -92,7 +92,7 @@ open class Crc32( init: Int = 0xFFFFFFFF.toInt(), refin: Boolean = true, refout: Boolean = true, - xorOut: Int = 0xFFFFFFFF.toInt() + xorOut: Int = 0xFFFFFFFF.toInt(), ) : Checksum { /** Default CRC-32/ISO-HDLC instance. */ companion object Default : Crc32() diff --git a/src/commonMain/kotlin/com/eignex/kencode/EncodedFormat.kt b/src/commonMain/kotlin/com/eignex/kencode/EncodedFormat.kt index 753425a..e668deb 100644 --- a/src/commonMain/kotlin/com/eignex/kencode/EncodedFormat.kt +++ b/src/commonMain/kotlin/com/eignex/kencode/EncodedFormat.kt @@ -34,9 +34,7 @@ data class EncodedConfiguration( * @property configuration The active configuration dictating the codec, transform, and binary format. */ @OptIn(ExperimentalSerializationApi::class) -open class EncodedFormat( - val configuration: EncodedConfiguration, -) : StringFormat { +open class EncodedFormat(val configuration: EncodedConfiguration) : StringFormat { /** * Secondary constructor for direct instantiation without the builder. @@ -61,10 +59,7 @@ open class EncodedFormat( * Serializes [value] with the configured binary format, applies the transform, * and encodes the resulting byte array using the text codec. */ - override fun encodeToString( - serializer: SerializationStrategy, - value: T - ): String { + override fun encodeToString(serializer: SerializationStrategy, value: T): String { val bytes = configuration.binaryFormat.encodeToByteArray(serializer, value) val payload = configuration.transform?.encode(bytes) ?: bytes @@ -77,15 +72,12 @@ open class EncodedFormat( * * @throws IllegalArgumentException if the transform's decode step fails (e.g. checksum mismatch). */ - override fun decodeFromString( - deserializer: DeserializationStrategy, - string: String - ): T { + override fun decodeFromString(deserializer: DeserializationStrategy, string: String): T { val input = configuration.codec.decode(string) val bytes = configuration.transform?.decode(input) ?: input return configuration.binaryFormat.decodeFromByteArray( deserializer, - bytes + bytes, ) } } @@ -129,7 +121,7 @@ class EncodedFormatBuilder { */ fun EncodedFormat( from: EncodedFormat = EncodedFormat.Default, - builderAction: EncodedFormatBuilder.() -> Unit + builderAction: EncodedFormatBuilder.() -> Unit, ): EncodedFormat { val builder = EncodedFormatBuilder().apply { codec = from.configuration.codec @@ -142,6 +134,6 @@ fun EncodedFormat( builder.codec, builder.transform, builder.binaryFormat, - ) + ), ) } diff --git a/src/commonMain/kotlin/com/eignex/kencode/PackedContext.kt b/src/commonMain/kotlin/com/eignex/kencode/PackedContext.kt index 505499a..648cb89 100644 --- a/src/commonMain/kotlin/com/eignex/kencode/PackedContext.kt +++ b/src/commonMain/kotlin/com/eignex/kencode/PackedContext.kt @@ -40,21 +40,15 @@ internal class ClassBitmask(descriptor: SerialDescriptor) { val totalCount: Int get() = boolCount + nullCount - fun booleanPos(fieldIdx: Int): Int = - booleanLookup.getOrElse(fieldIdx) { -1 } + fun booleanPos(fieldIdx: Int): Int = booleanLookup.getOrElse(fieldIdx) { -1 } - fun nullablePos(fieldIdx: Int): Int = - nullableLookup.getOrElse(fieldIdx) { -1 } + fun nullablePos(fieldIdx: Int): Int = nullableLookup.getOrElse(fieldIdx) { -1 } /** * Packs boolean values followed by null values into a fixed-width byte array. * Used for inline (non-merged) bitmasks in polymorphic and other non-CLASS structures. */ - fun writeInlineBitmask( - booleanValues: BooleanArray, - nullValues: BooleanArray, - out: ByteOutput - ) { + fun writeInlineBitmask(booleanValues: BooleanArray, nullValues: BooleanArray, out: ByteOutput) { val n = totalCount if (n == 0) return val bytes = ByteArray((n + 7) / 8) @@ -86,11 +80,10 @@ internal fun shouldMergeChildCtx( parentIsCollection: Boolean, parentDescriptor: SerialDescriptor, fieldIndex: Int, - childDescriptor: SerialDescriptor -): Boolean = - parentIsMergedKind && !parentIsCollection && fieldIndex >= 0 && - !parentDescriptor.getElementDescriptor(fieldIndex).isNullable && !childDescriptor.isInline && - (childDescriptor.kind is StructureKind.CLASS || childDescriptor.kind is StructureKind.OBJECT) + childDescriptor: SerialDescriptor, +): Boolean = parentIsMergedKind && !parentIsCollection && fieldIndex >= 0 && + !parentDescriptor.getElementDescriptor(fieldIndex).isNullable && !childDescriptor.isInline && + (childDescriptor.kind is StructureKind.CLASS || childDescriptor.kind is StructureKind.OBJECT) /** * Accumulates bitmask bits across an entire nested-class hierarchy so that the @@ -101,8 +94,7 @@ internal class HeaderContext { private var readCursor = 0 /** Reserves slots and returns the start index for a later set call. */ - fun reserve(count: Int): Int = - bits.size.also { repeat(count) { bits.add(false) } } + fun reserve(count: Int): Int = bits.size.also { repeat(count) { bits.add(false) } } /** Fills the slots starting at [start] with booleans immediately followed by nulls. */ fun set(start: Int, booleans: BooleanArray, nulls: BooleanArray) { diff --git a/src/commonMain/kotlin/com/eignex/kencode/PackedDecoder.kt b/src/commonMain/kotlin/com/eignex/kencode/PackedDecoder.kt index d7b79db..009bfb3 100644 --- a/src/commonMain/kotlin/com/eignex/kencode/PackedDecoder.kt +++ b/src/commonMain/kotlin/com/eignex/kencode/PackedDecoder.kt @@ -22,7 +22,8 @@ class PackedDecoder internal constructor( private val config: PackedConfiguration, override val serializersModule: SerializersModule, headerCtx: HeaderContext?, -) : Decoder, CompositeDecoder { +) : Decoder, + CompositeDecoder { constructor( input: ByteArray, @@ -176,27 +177,25 @@ class PackedDecoder internal constructor( override fun decodeShort(): Short = readShortPos() - override fun decodeInt(): Int = - if (inStructure && !isCollection) { - decodeIntElement(currentDescriptor, currentIndex) - } else { - when (config.defaultEncoding) { - IntPacking.SIGNED -> zigZagDecodeInt(readVarInt()) - IntPacking.DEFAULT -> readVarInt() - IntPacking.FIXED -> readIntPos() - } + override fun decodeInt(): Int = if (inStructure && !isCollection) { + decodeIntElement(currentDescriptor, currentIndex) + } else { + when (config.defaultEncoding) { + IntPacking.SIGNED -> zigZagDecodeInt(readVarInt()) + IntPacking.DEFAULT -> readVarInt() + IntPacking.FIXED -> readIntPos() } + } - override fun decodeLong(): Long = - if (inStructure && !isCollection) { - decodeLongElement(currentDescriptor, currentIndex) - } else { - when (config.defaultEncoding) { - IntPacking.SIGNED -> zigZagDecodeLong(readVarLong()) - IntPacking.DEFAULT -> readVarLong() - IntPacking.FIXED -> readLongPos() - } + override fun decodeLong(): Long = if (inStructure && !isCollection) { + decodeLongElement(currentDescriptor, currentIndex) + } else { + when (config.defaultEncoding) { + IntPacking.SIGNED -> zigZagDecodeLong(readVarLong()) + IntPacking.DEFAULT -> readVarLong() + IntPacking.FIXED -> readLongPos() } + } override fun decodeFloat(): Float = Float.fromBits(readIntPos()) @@ -213,8 +212,7 @@ class PackedDecoder internal constructor( } @ExperimentalSerializationApi - override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = - readVarInt() + override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = readVarInt() @ExperimentalSerializationApi override fun decodeNull(): Nothing? = null @@ -248,85 +246,51 @@ class PackedDecoder internal constructor( currentIndex = -1 } - override fun decodeBooleanElement( - descriptor: SerialDescriptor, - index: Int - ): Boolean { + override fun decodeBooleanElement(descriptor: SerialDescriptor, index: Int): Boolean { val pos = bitmask.booleanPos(index) if (pos == -1) error("Element $index is not a boolean") return booleanValues[pos] } - override fun decodeByteElement( - descriptor: SerialDescriptor, - index: Int - ): Byte { + override fun decodeByteElement(descriptor: SerialDescriptor, index: Int): Byte { require(position < input.size) return input[position++] } - override fun decodeShortElement( - descriptor: SerialDescriptor, - index: Int - ): Short = readShortPos() + override fun decodeShortElement(descriptor: SerialDescriptor, index: Int): Short = readShortPos() - override fun decodeIntElement( - descriptor: SerialDescriptor, - index: Int - ): Int = - when ( - resolveIntEncoding( - descriptor.getElementAnnotations(index), - config - ) - ) { - IntPacking.SIGNED -> zigZagDecodeInt(readVarInt()) - IntPacking.DEFAULT -> readVarInt() - IntPacking.FIXED -> readIntPos() - } + override fun decodeIntElement(descriptor: SerialDescriptor, index: Int): Int = when ( + resolveIntEncoding( + descriptor.getElementAnnotations(index), + config, + ) + ) { + IntPacking.SIGNED -> zigZagDecodeInt(readVarInt()) + IntPacking.DEFAULT -> readVarInt() + IntPacking.FIXED -> readIntPos() + } - override fun decodeLongElement( - descriptor: SerialDescriptor, - index: Int - ): Long = - when ( - resolveIntEncoding( - descriptor.getElementAnnotations(index), - config - ) - ) { - IntPacking.SIGNED -> zigZagDecodeLong(readVarLong()) - IntPacking.DEFAULT -> readVarLong() - IntPacking.FIXED -> readLongPos() - } + override fun decodeLongElement(descriptor: SerialDescriptor, index: Int): Long = when ( + resolveIntEncoding( + descriptor.getElementAnnotations(index), + config, + ) + ) { + IntPacking.SIGNED -> zigZagDecodeLong(readVarLong()) + IntPacking.DEFAULT -> readVarLong() + IntPacking.FIXED -> readLongPos() + } - override fun decodeFloatElement( - descriptor: SerialDescriptor, - index: Int - ): Float = - Float.fromBits(readIntPos()) + override fun decodeFloatElement(descriptor: SerialDescriptor, index: Int): Float = Float.fromBits(readIntPos()) - override fun decodeDoubleElement( - descriptor: SerialDescriptor, - index: Int - ): Double = - Double.fromBits(readLongPos()) + override fun decodeDoubleElement(descriptor: SerialDescriptor, index: Int): Double = Double.fromBits(readLongPos()) - override fun decodeCharElement( - descriptor: SerialDescriptor, - index: Int - ): Char = readUtf8Char() + override fun decodeCharElement(descriptor: SerialDescriptor, index: Int): Char = readUtf8Char() - override fun decodeStringElement( - descriptor: SerialDescriptor, - index: Int - ): String = readStringInline() + override fun decodeStringElement(descriptor: SerialDescriptor, index: Int): String = readStringInline() @ExperimentalSerializationApi - override fun decodeInlineElement( - descriptor: SerialDescriptor, - index: Int - ): Decoder { + override fun decodeInlineElement(descriptor: SerialDescriptor, index: Int): Decoder { currentIndex = index return this } @@ -335,7 +299,7 @@ class PackedDecoder internal constructor( descriptor: SerialDescriptor, index: Int, deserializer: DeserializationStrategy, - previousValue: T? + previousValue: T?, ): T { currentIndex = index @@ -367,13 +331,13 @@ class PackedDecoder internal constructor( isCollection, descriptor, index, - deserializer.descriptor + deserializer.descriptor, ) val subDecoder = PackedDecoder( input, config, serializersModule, - if (shouldShareCtx) ctx else null + if (shouldShareCtx) ctx else null, ) subDecoder.position = this.position val value = deserializer.deserialize(subDecoder) @@ -392,7 +356,7 @@ class PackedDecoder internal constructor( descriptor: SerialDescriptor, index: Int, deserializer: DeserializationStrategy, - previousValue: T? + previousValue: T?, ): T? { require(!isCollection) { "decodeNullableSerializableElement should not be called for collections." @@ -409,7 +373,7 @@ class PackedDecoder internal constructor( descriptor, index, deserializer as DeserializationStrategy, - previousValue + previousValue, ) } @@ -440,20 +404,18 @@ class PackedDecoder internal constructor( } } - private fun readShortPos(): Short = - readShort(input, position).also { position += 2 } + private fun readShortPos(): Short = readShort(input, position).also { position += 2 } - private fun readIntPos(): Int = - readInt(input, position).also { position += 4 } + private fun readIntPos(): Int = readInt(input, position).also { position += 4 } - private fun readLongPos(): Long = - readLong(input, position).also { position += 8 } + private fun readLongPos(): Long = readLong(input, position).also { position += 8 } private fun readUtf8Char(): Char { require(position < input.size) { "Unexpected EOF while decoding UTF-8 char" } val b0 = input[position].toInt() and 0xFF val (len, cp) = when { (b0 and 0b1000_0000) == 0 -> 1 to b0 + (b0 and 0b1110_0000) == 0b1100_0000 -> { require(position + 2 <= input.size) val b1 = input[position + 1].toInt() and 0xFF @@ -471,7 +433,8 @@ class PackedDecoder internal constructor( } else -> throw IllegalArgumentException( - "Code points above U+FFFF (4-byte UTF-8 sequences) are not supported in Char fields; use String instead" + "Code points above U+FFFF (4-byte UTF-8 sequences) are not supported in Char fields; " + + "use String instead", ) } position += len diff --git a/src/commonMain/kotlin/com/eignex/kencode/PackedEncoder.kt b/src/commonMain/kotlin/com/eignex/kencode/PackedEncoder.kt index 43e13b1..aeae340 100644 --- a/src/commonMain/kotlin/com/eignex/kencode/PackedEncoder.kt +++ b/src/commonMain/kotlin/com/eignex/kencode/PackedEncoder.kt @@ -20,7 +20,8 @@ class PackedEncoder internal constructor( private val config: PackedConfiguration = PackedConfiguration(), override val serializersModule: SerializersModule = EmptySerializersModule(), headerCtx: HeaderContext? = null, -) : Encoder, CompositeEncoder { +) : Encoder, + CompositeEncoder { private val ctx: HeaderContext = headerCtx ?: HeaderContext() private val isRoot: Boolean = headerCtx == null @@ -58,7 +59,7 @@ class PackedEncoder internal constructor( isCollection, currentDescriptor, currentIndex, - descriptor + descriptor, ) ) { ctx @@ -74,10 +75,7 @@ class PackedEncoder internal constructor( return this } - override fun beginCollection( - descriptor: SerialDescriptor, - collectionSize: Int - ): CompositeEncoder { + override fun beginCollection(descriptor: SerialDescriptor, collectionSize: Int): CompositeEncoder { val target = if (inStructure) dataBuffer else output writeVarInt(collectionSize, target) val childEncoder = PackedEncoder(target, config, serializersModule) @@ -117,19 +115,18 @@ class PackedEncoder internal constructor( dataBuffer.reset() } - private fun getBuffer(): ByteOutput = - if (inStructure) dataBuffer else output + private fun getBuffer(): ByteOutput = if (inStructure) dataBuffer else output override fun encodeBoolean(value: Boolean) { when { inStructure && !isCollection -> encodeBooleanElement( currentDescriptor, currentIndex, - value + value, ) inStructure && isBooleanCollection -> collectionBitmapValues.add( - value + value, ) else -> getBuffer().write(if (value) 1 else 0) @@ -151,14 +148,14 @@ class PackedEncoder internal constructor( when (config.defaultEncoding) { IntPacking.SIGNED -> writeVarInt( zigZagEncodeInt( - value + value, ), - getBuffer() + getBuffer(), ) IntPacking.DEFAULT -> writeVarInt( value, - getBuffer() + getBuffer(), ) IntPacking.FIXED -> writeInt(value, getBuffer()) @@ -173,14 +170,14 @@ class PackedEncoder internal constructor( when (config.defaultEncoding) { IntPacking.SIGNED -> writeVarLong( zigZagEncodeLong( - value + value, ), - getBuffer() + getBuffer(), ) IntPacking.DEFAULT -> writeVarLong( value, - getBuffer() + getBuffer(), ) IntPacking.FIXED -> writeLong(value, getBuffer()) @@ -281,117 +278,80 @@ class PackedEncoder internal constructor( currentIndex = -1 } - override fun encodeBooleanElement( - descriptor: SerialDescriptor, - index: Int, - value: Boolean - ) { + override fun encodeBooleanElement(descriptor: SerialDescriptor, index: Int, value: Boolean) { val pos = bitmask.booleanPos(index) if (pos == -1) error("Element $index is not a boolean") booleanValues[pos] = value } - override fun encodeIntElement( - descriptor: SerialDescriptor, - index: Int, - value: Int - ) { + override fun encodeIntElement(descriptor: SerialDescriptor, index: Int, value: Int) { when ( resolveIntEncoding( descriptor.getElementAnnotations(index), - config + config, ) ) { IntPacking.SIGNED -> writeVarInt( zigZagEncodeInt( - value + value, ), - dataBuffer + dataBuffer, ) IntPacking.DEFAULT -> writeVarInt(value, dataBuffer) + IntPacking.FIXED -> writeInt(value, dataBuffer) } } - override fun encodeLongElement( - descriptor: SerialDescriptor, - index: Int, - value: Long - ) { + override fun encodeLongElement(descriptor: SerialDescriptor, index: Int, value: Long) { when ( resolveIntEncoding( descriptor.getElementAnnotations(index), - config + config, ) ) { IntPacking.SIGNED -> writeVarLong( zigZagEncodeLong( - value + value, ), - dataBuffer + dataBuffer, ) IntPacking.DEFAULT -> writeVarLong(value, dataBuffer) + IntPacking.FIXED -> writeLong(value, dataBuffer) } } - override fun encodeByteElement( - descriptor: SerialDescriptor, - index: Int, - value: Byte - ) { + override fun encodeByteElement(descriptor: SerialDescriptor, index: Int, value: Byte) { dataBuffer.write(value.toInt() and 0xFF) } - override fun encodeShortElement( - descriptor: SerialDescriptor, - index: Int, - value: Short - ) { + override fun encodeShortElement(descriptor: SerialDescriptor, index: Int, value: Short) { writeShort(value, dataBuffer) } - override fun encodeCharElement( - descriptor: SerialDescriptor, - index: Int, - value: Char - ) { + override fun encodeCharElement(descriptor: SerialDescriptor, index: Int, value: Char) { writeUtf8Char(value, dataBuffer) } - override fun encodeFloatElement( - descriptor: SerialDescriptor, - index: Int, - value: Float - ) { + override fun encodeFloatElement(descriptor: SerialDescriptor, index: Int, value: Float) { writeInt(value.toBits(), dataBuffer) } - override fun encodeDoubleElement( - descriptor: SerialDescriptor, - index: Int, - value: Double - ) { + override fun encodeDoubleElement(descriptor: SerialDescriptor, index: Int, value: Double) { writeLong(value.toBits(), dataBuffer) } - override fun encodeStringElement( - descriptor: SerialDescriptor, - index: Int, - value: String - ) { + override fun encodeStringElement(descriptor: SerialDescriptor, index: Int, value: String) { val bytes = value.encodeToByteArray() writeVarInt(bytes.size, dataBuffer) dataBuffer.write(bytes) } @ExperimentalSerializationApi - override fun encodeInlineElement( - descriptor: SerialDescriptor, - index: Int - ): Encoder { + override fun encodeInlineElement(descriptor: SerialDescriptor, index: Int): Encoder { currentIndex = index return this } @@ -400,7 +360,7 @@ class PackedEncoder internal constructor( descriptor: SerialDescriptor, index: Int, serializer: SerializationStrategy, - value: T + value: T, ) { currentIndex = index serializer.serialize(this, value) @@ -412,7 +372,7 @@ class PackedEncoder internal constructor( descriptor: SerialDescriptor, index: Int, serializer: SerializationStrategy, - value: T? + value: T?, ) { if (isCollection) { if (value == null) { @@ -438,6 +398,7 @@ class PackedEncoder internal constructor( val cp = value.code when { cp < 0x80 -> out.write(cp) + cp < 0x800 -> { out.write(0xC0 or (cp shr 6)) out.write(0x80 or (cp and 0x3F)) diff --git a/src/commonMain/kotlin/com/eignex/kencode/PackedFormat.kt b/src/commonMain/kotlin/com/eignex/kencode/PackedFormat.kt index d6efb4a..3681ae7 100644 --- a/src/commonMain/kotlin/com/eignex/kencode/PackedFormat.kt +++ b/src/commonMain/kotlin/com/eignex/kencode/PackedFormat.kt @@ -13,9 +13,7 @@ import kotlinx.serialization.modules.SerializersModule * @property defaultEncoding The encoding applied to all `Int` and `Long` fields that carry no * [PackedType] annotation. Defaults to [IntPacking.DEFAULT] varint encoding. */ -data class PackedConfiguration( - val defaultEncoding: IntPacking = IntPacking.DEFAULT -) +data class PackedConfiguration(val defaultEncoding: IntPacking = IntPacking.DEFAULT) /** * Compact `BinaryFormat` optimized for small, flat Kotlin data classes. @@ -37,7 +35,7 @@ data class PackedConfiguration( @OptIn(ExperimentalSerializationApi::class) open class PackedFormat( val configuration: PackedConfiguration = PackedConfiguration(), - override val serializersModule: SerializersModule = EmptySerializersModule() + override val serializersModule: SerializersModule = EmptySerializersModule(), ) : BinaryFormat { /** @@ -48,10 +46,7 @@ open class PackedFormat( /** * Encodes [value] into a compact binary representation. */ - override fun encodeToByteArray( - serializer: SerializationStrategy, - value: T - ): ByteArray { + override fun encodeToByteArray(serializer: SerializationStrategy, value: T): ByteArray { val out = ByteOutput() val encoder = PackedEncoder(out, configuration, serializersModule) encoder.encodeSerializableValue(serializer, value) @@ -61,10 +56,7 @@ open class PackedFormat( /** * Decodes [bytes] produced by [PackedFormat] back into an object of type [T]. */ - override fun decodeFromByteArray( - deserializer: DeserializationStrategy, - bytes: ByteArray - ): T { + override fun decodeFromByteArray(deserializer: DeserializationStrategy, bytes: ByteArray): T { val decoder = PackedDecoder(bytes, configuration, serializersModule) return decoder.decodeSerializableValue(deserializer) } @@ -101,7 +93,7 @@ class PackedFormatBuilder { */ fun PackedFormat( from: PackedFormat = PackedFormat.Default, - builderAction: PackedFormatBuilder.() -> Unit + builderAction: PackedFormatBuilder.() -> Unit, ): PackedFormat { val builder = PackedFormatBuilder().apply { serializersModule = from.serializersModule @@ -111,6 +103,6 @@ fun PackedFormat( return PackedFormat( configuration = PackedConfiguration(builder.defaultEncoding), - serializersModule = builder.serializersModule + serializersModule = builder.serializersModule, ) } diff --git a/src/commonMain/kotlin/com/eignex/kencode/PackedUtils.kt b/src/commonMain/kotlin/com/eignex/kencode/PackedUtils.kt index 4c4e176..84175a4 100644 --- a/src/commonMain/kotlin/com/eignex/kencode/PackedUtils.kt +++ b/src/commonMain/kotlin/com/eignex/kencode/PackedUtils.kt @@ -1,11 +1,6 @@ package com.eignex.kencode -private fun requireAvailable( - data: ByteArray, - offset: Int, - needed: Int, - what: String -) { +private fun requireAvailable(data: ByteArray, offset: Int, needed: Int, what: String) { require(offset >= 0 && needed >= 0 && offset + needed <= data.size) { "Unexpected EOF while decoding $what: need $needed bytes " + "from offset=$offset, size=${data.size}" } diff --git a/src/commonMain/kotlin/com/eignex/kencode/PayloadTransform.kt b/src/commonMain/kotlin/com/eignex/kencode/PayloadTransform.kt index dcc6606..fcf97f2 100644 --- a/src/commonMain/kotlin/com/eignex/kencode/PayloadTransform.kt +++ b/src/commonMain/kotlin/com/eignex/kencode/PayloadTransform.kt @@ -35,14 +35,11 @@ interface Checksum { * * Example: `CompactZeros.then(Crc16.asTransform())` compacts bytes, then appends a checksum. */ -fun PayloadTransform.then(next: PayloadTransform): PayloadTransform = - object : PayloadTransform { - override fun encode(data: ByteArray): ByteArray = - next.encode(this@then.encode(data)) +fun PayloadTransform.then(next: PayloadTransform): PayloadTransform = object : PayloadTransform { + override fun encode(data: ByteArray): ByteArray = next.encode(this@then.encode(data)) - override fun decode(data: ByteArray): ByteArray = - this@then.decode(next.decode(data)) - } + override fun decode(data: ByteArray): ByteArray = this@then.decode(next.decode(data)) +} /** * Strips leading zero bytes before encoding and restores them on decode. @@ -66,7 +63,7 @@ object CompactZeros : PayloadTransform { data.copyInto( result, destinationOffset = 1 + count.size, - startIndex = k + startIndex = k, ) return result } diff --git a/src/commonTest/kotlin/com/eignex/kencode/Base64Test.kt b/src/commonTest/kotlin/com/eignex/kencode/Base64Test.kt index 392fca3..21c0602 100644 --- a/src/commonTest/kotlin/com/eignex/kencode/Base64Test.kt +++ b/src/commonTest/kotlin/com/eignex/kencode/Base64Test.kt @@ -5,11 +5,7 @@ import kotlin.test.* class Base64Test { - private fun assertRoundtrip( - codec: Base64, - bytes: ByteArray, - message: String? = null - ) { + private fun assertRoundtrip(codec: Base64, bytes: ByteArray, message: String? = null) { val encoded = codec.encode(bytes) val decoded = codec.decode(encoded) @@ -51,14 +47,14 @@ class Base64Test { byteArrayOf(-1), byteArrayOf(0, 1, 2, 3, 4), byteArrayOf(-1, -2, -3, -4), - byteArrayOf(127, -128, 0, 42) + byteArrayOf(127, -128, 0, 42), ) for (bytes in patterns) { assertRoundtrip( Base64, bytes, - "Failed for pattern=${bytes.toList()}" + "Failed for pattern=${bytes.toList()}", ) } } @@ -72,14 +68,14 @@ class Base64Test { byteArrayOf(-1), byteArrayOf(0, 1, 2, 3, 4), byteArrayOf(-1, -2, -3, -4), - byteArrayOf(127, -128, 0, 42) + byteArrayOf(127, -128, 0, 42), ) for (bytes in patterns) { assertRoundtrip( Base64Url, bytes, - "Failed for pattern=${bytes.toList()}" + "Failed for pattern=${bytes.toList()}", ) } } @@ -147,7 +143,7 @@ class Base64Test { "foo" to "Zm9v", "foob" to "Zm9vYg==", "fooba" to "Zm9vYmE=", - "foobar" to "Zm9vYmFy" + "foobar" to "Zm9vYmFy", ) for ((plain, expectedEncoded) in vectors) { @@ -156,14 +152,14 @@ class Base64Test { assertEquals( expectedEncoded, encoded, - "Encoding mismatch for '$plain'" + "Encoding mismatch for '$plain'", ) val decoded = Base64.decode(expectedEncoded) assertContentEquals( bytes, decoded, - "Decoding mismatch for '$plain'" + "Decoding mismatch for '$plain'", ) } } diff --git a/src/commonTest/kotlin/com/eignex/kencode/Base85Test.kt b/src/commonTest/kotlin/com/eignex/kencode/Base85Test.kt index a8c4413..dd841bc 100644 --- a/src/commonTest/kotlin/com/eignex/kencode/Base85Test.kt +++ b/src/commonTest/kotlin/com/eignex/kencode/Base85Test.kt @@ -5,10 +5,7 @@ import kotlin.test.* class Base85Test { - private fun assertRoundtrip( - bytes: ByteArray, - message: String? = null - ) { + private fun assertRoundtrip(bytes: ByteArray, message: String? = null) { val encoded = Base85.encode(bytes) val decoded = Base85.decode(encoded) @@ -42,13 +39,13 @@ class Base85Test { byteArrayOf(-1), byteArrayOf(0, 1, 2, 3, 4), byteArrayOf(-1, -2, -3, -4), - byteArrayOf(127, -128, 0, 42) + byteArrayOf(127, -128, 0, 42), ) for (bytes in patterns) { assertRoundtrip( bytes, - "ASCII85 failed for pattern=${bytes.toList()}" + "ASCII85 failed for pattern=${bytes.toList()}", ) } } diff --git a/src/commonTest/kotlin/com/eignex/kencode/BaseRadixTest.kt b/src/commonTest/kotlin/com/eignex/kencode/BaseRadixTest.kt index 01c3d01..2250144 100644 --- a/src/commonTest/kotlin/com/eignex/kencode/BaseRadixTest.kt +++ b/src/commonTest/kotlin/com/eignex/kencode/BaseRadixTest.kt @@ -12,14 +12,10 @@ class BaseRadixTest { byteArrayOf(-1), byteArrayOf(0, 1, 2, 3, 4), byteArrayOf(-1, -2, -3, -4), - byteArrayOf(127, -128, 0, 42) + byteArrayOf(127, -128, 0, 42), ) - private fun assertRoundtrip( - codec: BaseRadix, - bytes: ByteArray, - message: String? = null - ) { + private fun assertRoundtrip(codec: BaseRadix, bytes: ByteArray, message: String? = null) { val encoded = codec.encode(bytes) val decoded = codec.decode(encoded) if (message != null) { @@ -40,7 +36,7 @@ class BaseRadixTest { assertRoundtrip( codec, bytes, - "Roundtrip failed for ${codec::class.simpleName} length=$len" + "Roundtrip failed for ${codec::class.simpleName} length=$len", ) } } @@ -52,7 +48,7 @@ class BaseRadixTest { assertRoundtrip( codec, bytes, - "Failed for ${codec::class.simpleName} pattern=${bytes.toList()}" + "Failed for ${codec::class.simpleName} pattern=${bytes.toList()}", ) } } @@ -106,8 +102,8 @@ class BaseRadixTest { assertFailsWith { Base62.decode( "!" + encoded.drop( - 1 - ) + 1, + ), ) } } @@ -149,9 +145,9 @@ class BaseRadixTest { 0xDE.toByte(), 0xAD.toByte(), 0xBE.toByte(), - 0xEF.toByte() - ) - ) + 0xEF.toByte(), + ), + ), ) } @@ -163,7 +159,7 @@ class BaseRadixTest { assertRoundtrip( codec, bytes, - "Multi-block failed for ${codec::class.simpleName}" + "Multi-block failed for ${codec::class.simpleName}", ) } } @@ -177,7 +173,7 @@ class BaseRadixTest { assertRoundtrip( codec, bytes, - "Custom blockSize=4 roundtrip failed for len=$len" + "Custom blockSize=4 roundtrip failed for len=$len", ) } } @@ -261,7 +257,7 @@ class BaseRadixTest { 0x80.toByte(), 0x7F.toByte(), 0x00, - 0xFF.toByte() + 0xFF.toByte(), ) assertRoundtrip(codec, input, "High bit bytes failed on massive base") } diff --git a/src/commonTest/kotlin/com/eignex/kencode/CrcTest.kt b/src/commonTest/kotlin/com/eignex/kencode/CrcTest.kt index 431d545..5db66d4 100644 --- a/src/commonTest/kotlin/com/eignex/kencode/CrcTest.kt +++ b/src/commonTest/kotlin/com/eignex/kencode/CrcTest.kt @@ -10,19 +10,16 @@ class CrcTest { s.substring(i * 2, i * 2 + 2).toInt(16).toByte() } - private fun Checksum.digest(s: String): ByteArray = - digest(s.encodeToByteArray()) + private fun Checksum.digest(s: String): ByteArray = digest(s.encodeToByteArray()) @Test fun `crc8 size is one byte`() = assertEquals(1, Crc8.size) @Test - fun `crc8 smbus check value 123456789`() = - assertContentEquals(hex("F4"), Crc8.digest("123456789")) + fun `crc8 smbus check value 123456789`() = assertContentEquals(hex("F4"), Crc8.digest("123456789")) @Test - fun `crc8 smbus empty input`() = - assertContentEquals(hex("00"), Crc8.digest("")) + fun `crc8 smbus empty input`() = assertContentEquals(hex("00"), Crc8.digest("")) @Test fun `crc8 rohc check value 123456789`() { @@ -31,7 +28,7 @@ class CrcTest { init = 0xFF, refin = true, refout = true, - xorOut = 0x00 + xorOut = 0x00, ) assertContentEquals(hex("D0"), rohc.digest("123456789")) } @@ -40,12 +37,10 @@ class CrcTest { fun `crc16 size is two bytes`() = assertEquals(2, Crc16.size) @Test - fun `crc16 x25 check value 123456789`() = - assertContentEquals(hex("906E"), Crc16.digest("123456789")) + fun `crc16 x25 check value 123456789`() = assertContentEquals(hex("906E"), Crc16.digest("123456789")) @Test - fun `crc16 x25 empty input`() = - assertContentEquals(hex("0000"), Crc16.digest("")) + fun `crc16 x25 empty input`() = assertContentEquals(hex("0000"), Crc16.digest("")) @Test fun `crc16 ccitt-false check value 123456789`() { @@ -54,7 +49,7 @@ class CrcTest { init = 0xFFFF, refin = false, refout = false, - xorOut = 0x0000 + xorOut = 0x0000, ) assertContentEquals(hex("29B1"), ccittFalse.digest("123456789")) } @@ -63,12 +58,10 @@ class CrcTest { fun `crc32 size is four bytes`() = assertEquals(4, Crc32.size) @Test - fun `crc32 iso-hdlc check value 123456789`() = - assertContentEquals(hex("CBF43926"), Crc32.digest("123456789")) + fun `crc32 iso-hdlc check value 123456789`() = assertContentEquals(hex("CBF43926"), Crc32.digest("123456789")) @Test - fun `crc32 iso-hdlc empty input`() = - assertContentEquals(hex("00000000"), Crc32.digest("")) + fun `crc32 iso-hdlc empty input`() = assertContentEquals(hex("00000000"), Crc32.digest("")) @Test fun `crc32 mpeg-2 check value 123456789`() { @@ -77,7 +70,7 @@ class CrcTest { init = 0xFFFFFFFF.toInt(), refin = false, refout = false, - xorOut = 0x00000000 + xorOut = 0x00000000, ) assertContentEquals(hex("0376E6E7"), mpeg2.digest("123456789")) } diff --git a/src/commonTest/kotlin/com/eignex/kencode/EncodedFormatTest.kt b/src/commonTest/kotlin/com/eignex/kencode/EncodedFormatTest.kt index b046f27..5857d64 100644 --- a/src/commonTest/kotlin/com/eignex/kencode/EncodedFormatTest.kt +++ b/src/commonTest/kotlin/com/eignex/kencode/EncodedFormatTest.kt @@ -58,7 +58,7 @@ class EncodedFormatTest { formatBare.encodeToString(Payload.serializer(), value) assertTrue( encoded.length <= uncompressed.length, - "compactZeros ($encoded) should be <= uncompressed ($uncompressed)" + "compactZeros ($encoded) should be <= uncompressed ($uncompressed)", ) } @@ -85,7 +85,7 @@ class EncodedFormatTest { assertFailsWith { formatCompactChecksum.decodeFromString( Payload.serializer(), - tampered + tampered, ) } } @@ -152,8 +152,8 @@ class EncodedFormatTest { assertFailsWith { transform.decode( byteArrayOf( - 0x01 - ) + 0x01, + ), ) } assertFailsWith { transform.decode(byteArrayOf()) } diff --git a/src/commonTest/kotlin/com/eignex/kencode/PackedContextTest.kt b/src/commonTest/kotlin/com/eignex/kencode/PackedContextTest.kt index c6f4b36..1c2a681 100644 --- a/src/commonTest/kotlin/com/eignex/kencode/PackedContextTest.kt +++ b/src/commonTest/kotlin/com/eignex/kencode/PackedContextTest.kt @@ -91,7 +91,7 @@ class PackedContextTest { bitmask.writeInlineBitmask( booleanArrayOf(true), booleanArrayOf(true), - out + out, ) val bytes = out.toByteArray() assertEquals(1, bytes.size) @@ -105,7 +105,7 @@ class PackedContextTest { bitmask.writeInlineBitmask( booleanArrayOf(false), booleanArrayOf(false), - out + out, ) val bytes = out.toByteArray() assertEquals(1, bytes.size) @@ -120,7 +120,7 @@ class PackedContextTest { bitmask.writeInlineBitmask( BooleanArray(64) { it == 0 }, BooleanArray(0), - out + out, ) val bytes = out.toByteArray() assertEquals(8, bytes.size) @@ -169,12 +169,12 @@ class PackedContextTest { ctx.set( start1, booleanArrayOf(true), - booleanArrayOf(false) + booleanArrayOf(false), ) ctx.set( start2, booleanArrayOf(false), - booleanArrayOf(true) + booleanArrayOf(true), ) val bytes = ctx.toByteArray() assertEquals(1, bytes.size) @@ -187,7 +187,7 @@ class PackedContextTest { assertEquals(1, HeaderContext().load(byteArrayOf(0xFF.toByte()), 0, 8)) assertEquals( 2, - HeaderContext().load(byteArrayOf(0xFF.toByte(), 0x00), 0, 9) + HeaderContext().load(byteArrayOf(0xFF.toByte(), 0x00), 0, 9), ) } @@ -237,7 +237,7 @@ class PackedContextTest { fun `countAllBits class with booleans only`() { assertEquals( 2, - countAllBits(SimpleIntsAndBooleans.serializer().descriptor) + countAllBits(SimpleIntsAndBooleans.serializer().descriptor), ) } @@ -255,7 +255,7 @@ class PackedContextTest { fun `countAllBits does not recurse into nullable nested class`() { assertEquals( 6, - countAllBits(NullableFieldsPayload.serializer().descriptor) + countAllBits(NullableFieldsPayload.serializer().descriptor), ) } @@ -284,7 +284,7 @@ class PackedContextTest { parentDescriptor = DeepNested.serializer().descriptor, fieldIndex = 1, childDescriptor = Level1.serializer().descriptor, - ) + ), ) } @@ -297,7 +297,7 @@ class PackedContextTest { parentDescriptor = DeepNested.serializer().descriptor, fieldIndex = 1, childDescriptor = Level1.serializer().descriptor, - ) + ), ) } @@ -310,7 +310,7 @@ class PackedContextTest { parentDescriptor = DeepNested.serializer().descriptor, fieldIndex = 1, childDescriptor = Level1.serializer().descriptor, - ) + ), ) } @@ -323,7 +323,7 @@ class PackedContextTest { parentDescriptor = DeepNested.serializer().descriptor, fieldIndex = -1, childDescriptor = Level1.serializer().descriptor, - ) + ), ) } @@ -336,7 +336,7 @@ class PackedContextTest { parentDescriptor = Level1.serializer().descriptor, fieldIndex = 1, childDescriptor = Level2.serializer().descriptor, - ) + ), ) } @@ -349,7 +349,7 @@ class PackedContextTest { parentDescriptor = InlineHeavyPayload.serializer().descriptor, fieldIndex = 4, childDescriptor = UserId.serializer().descriptor, - ) + ), ) } @@ -363,7 +363,7 @@ class PackedContextTest { parentDescriptor = WithList.serializer().descriptor, fieldIndex = 1, childDescriptor = listDesc, - ) + ), ) } } diff --git a/src/commonTest/kotlin/com/eignex/kencode/PackedFormatTest.kt b/src/commonTest/kotlin/com/eignex/kencode/PackedFormatTest.kt index 782e932..f0f1ed2 100644 --- a/src/commonTest/kotlin/com/eignex/kencode/PackedFormatTest.kt +++ b/src/commonTest/kotlin/com/eignex/kencode/PackedFormatTest.kt @@ -23,20 +23,16 @@ class PackedFormatTest { @Serializable private data class ProtoAnnotatedPayload( @ProtoType(ProtoIntegerType.SIGNED) val signed: Int, - @ProtoType(ProtoIntegerType.DEFAULT) val unsigned: Long + @ProtoType(ProtoIntegerType.DEFAULT) val unsigned: Long, ) - private fun assertPackedRoundtrip( - serializer: KSerializer, - value: T, - format: PackedFormat = PackedFormat - ) { + private fun assertPackedRoundtrip(serializer: KSerializer, value: T, format: PackedFormat = PackedFormat) { val bytes = format.encodeToByteArray(serializer, value) val decoded = format.decodeFromByteArray(serializer, bytes) assertEquals( value, decoded, - "Roundtrip failed for ${serializer.descriptor.serialName}" + "Roundtrip failed for ${serializer.descriptor.serialName}", ) } @@ -56,7 +52,7 @@ class PackedFormatTest { Double.NaN, 'A', true, - "Hello" + "Hello", ), AllPrimitiveTypes( -123, @@ -67,7 +63,7 @@ class PackedFormatTest { -2.75, 'ñ', true, - "Hello \uD83D\uDE80" + "Hello \uD83D\uDE80", ), AllPrimitiveTypesNullable( null, @@ -78,7 +74,7 @@ class PackedFormatTest { null, null, null, - null + null, ), NoBooleansNoNulls(42, 123_456_789L, "No flags here"), EnumPayload(1, Status.NEW, null), @@ -90,7 +86,7 @@ class PackedFormatTest { false, null, null, - "all-null-bools" + "all-null-bools", ), StringHeavyPayload("short", "longer", "unicode ✓✓✓", "present"), VarIntVarUIntPayload(-1, -123_456_789L, 0, 1L, 42, 9_999L), @@ -100,20 +96,20 @@ class PackedFormatTest { Int.MAX_VALUE, Long.MAX_VALUE, -1000, - 123_456L + 123_456L, ), UIntInlinePayload(1, 0u, 1u, true), DurationInlinePayload( "mixed", 5.seconds + 250.milliseconds, 2.minutes, - 10 + 10, ), InstantInlinePayload( Instant.fromEpochMilliseconds(0), null, true, - 1 + 1, ), Parent(id = 1, child = Child(2)), WithList(id = 1, items = listOf(1, 2, 3)), @@ -122,7 +118,7 @@ class PackedFormatTest { NestedOptionalList(2, null, emptyList()), DeepNested( "root", - Level1(true, Level2("payload", listOf(listOf(1, 2)))) + Level1(true, Level2("payload", listOf(listOf(1, 2)))), ), MapHolder(mapOf("host" to "localhost"), mapOf("retries" to 3)), ComplexMap(mapOf("day" to listOf(1)), mapOf(101 to Child(1))), @@ -130,7 +126,7 @@ class PackedFormatTest { 42, mapOf("k" to "v"), listOf(Child(1)), - listOf(true, null, false) + listOf(true, null, false), ), NullableMap(mapOf("k1" to "v1", "k2" to null)), BooleanFlags63(BooleanArray(63) { it % 2 == 0 }), @@ -147,10 +143,10 @@ class PackedFormatTest { listOf( RecursiveTree( "child1", - listOf(RecursiveTree("grandchild1")) + listOf(RecursiveTree("grandchild1")), ), - RecursiveTree("child2") - ) + RecursiveTree("child2"), + ), ), InlineHeavyPayload( @@ -161,7 +157,7 @@ class PackedFormatTest { UserId(9999L), Email("test@example.com"), listOf(UserId(1), UserId(2)), - Instant.DISTANT_PAST + Instant.DISTANT_PAST, ), MultiLevelCollections( @@ -170,19 +166,19 @@ class PackedFormatTest { 1 to listOf( "a", null, - "c" - ) - ) + "c", + ), + ), ), nestedLists = listOf(listOf(listOf(1, 2), listOf(3))), - optionalDeepMap = mapOf(42 to null) + optionalDeepMap = mapOf(42 to null), ), DeepBreadth( branchA = Level1(true, Level2("data-a", listOf(listOf(1)))), branchB = Level1(false, null), branchC = Level1(true, Level2("data-c", emptyList())), - rootValue = 999 + rootValue = 999, ), PolymorphicContainer( @@ -192,7 +188,7 @@ class PackedFormatTest { PolymorphicBase.Heartbeat, PolymorphicBase.Action("Update", 2), PolymorphicBase.Notification("Hello", true), - ) + ), ), PolymorphicBase.Heartbeat, @@ -223,7 +219,7 @@ class PackedFormatTest { 3.14f, 2.71828, "Stand-alone string", - Status.DONE + Status.DONE, ) primitives.forEach { value -> @@ -261,7 +257,7 @@ class PackedFormatTest { fun `invalid continuation byte should throw for top level char`() { val bytes = byteArrayOf( 0xC2.toByte(), - 0x41 + 0x41, ) val decoder = PackedDecoder(bytes) assertFailsWith { @@ -278,11 +274,11 @@ class PackedFormatTest { val fixedBytes = fixedFormat.encodeToByteArray( UnannotatedPayload.serializer(), - payload + payload, ) val defaultBytes = defaultFormat.encodeToByteArray( UnannotatedPayload.serializer(), - payload + payload, ) assertEquals(12, fixedBytes.size) @@ -292,8 +288,8 @@ class PackedFormatTest { payload, defaultFormat.decodeFromByteArray( UnannotatedPayload.serializer(), - defaultBytes - ) + defaultBytes, + ), ) } @@ -310,19 +306,19 @@ class PackedFormatTest { 12, fixedFormat.encodeToByteArray( UnannotatedPayload.serializer(), - payload - ).size + payload, + ).size, ) assertEquals( 15, varIntFormat.encodeToByteArray( UnannotatedPayload.serializer(), - payload - ).size + payload, + ).size, ) val zigZagBytes = zigZagFormat.encodeToByteArray( UnannotatedPayload.serializer(), - payload + payload, ) assertEquals(2, zigZagBytes.size) @@ -330,8 +326,8 @@ class PackedFormatTest { payload, zigZagFormat.decodeFromByteArray( UnannotatedPayload.serializer(), - zigZagBytes - ) + zigZagBytes, + ), ) } @@ -344,15 +340,15 @@ class PackedFormatTest { val bytes = optimizedFormat.encodeToByteArray( InversePayload.serializer(), - payload + payload, ) assertEquals(24, bytes.size) assertEquals( payload, optimizedFormat.decodeFromByteArray( InversePayload.serializer(), - bytes - ) + bytes, + ), ) } @@ -361,7 +357,7 @@ class PackedFormatTest { val serializer = AllPrimitiveTypes.serializer() val valid = PackedFormat.encodeToByteArray( serializer, - AllPrimitiveTypes(1, 1, 1, 1, 1f, 1.0, 'a', true, "test") + AllPrimitiveTypes(1, 1, 1, 1, 1f, 1.0, 'a', true, "test"), ) val truncated = valid.copyOfRange(0, valid.size - 5) @@ -374,7 +370,7 @@ class PackedFormatTest { fun `class with no booleans or nullables has no bitmask header`() { val bytes = PackedFormat.encodeToByteArray( NoBooleansNoNulls.serializer(), - NoBooleansNoNulls(42, 42L, "No flags here") + NoBooleansNoNulls(42, 42L, "No flags here"), ) assertEquals(16, bytes.size) } @@ -383,7 +379,7 @@ class PackedFormatTest { fun `boolean fields pack into single bitmask byte`() { val bytes = PackedFormat.encodeToByteArray( SimpleIntsAndBooleans.serializer(), - SimpleIntsAndBooleans(0, 0, false, false) + SimpleIntsAndBooleans(0, 0, false, false), ) assertEquals(3, bytes.size) } @@ -399,7 +395,7 @@ class PackedFormatTest { fun `empty list encodes as single zero byte`() { val bytes = PackedFormat.encodeToByteArray( ListSerializer(Int.serializer()), - emptyList() + emptyList(), ) assertEquals(1, bytes.size) assertEquals(0, bytes[0]) @@ -409,11 +405,11 @@ class PackedFormatTest { fun `empty map roundtrip`() { assertPackedRoundtrip( MapHolder.serializer(), - MapHolder(emptyMap(), null) + MapHolder(emptyMap(), null), ) assertPackedRoundtrip( MapHolder.serializer(), - MapHolder(emptyMap(), emptyMap()) + MapHolder(emptyMap(), emptyMap()), ) } @@ -422,14 +418,14 @@ class PackedFormatTest { val uintNegative = VarIntVarUIntPayload(0, 0L, -1, -1L, 0, 0) val bytes = PackedFormat.encodeToByteArray( VarIntVarUIntPayload.serializer(), - uintNegative + uintNegative, ) assertEquals(19, bytes.size) val intNegative = VarIntVarUIntPayload(-1, -1L, 0, 0L, 0, 0) val bytes2 = PackedFormat.encodeToByteArray( VarIntVarUIntPayload.serializer(), - intNegative + intNegative, ) assertEquals(6, bytes2.size) @@ -441,7 +437,7 @@ class PackedFormatTest { fun `deep null mid-chain roundtrip`() { assertPackedRoundtrip( DeepNested.serializer(), - DeepNested("root", Level1(false, null)) + DeepNested("root", Level1(false, null)), ) } @@ -449,7 +445,7 @@ class PackedFormatTest { fun `3-byte UTF-8 char roundtrip`() { assertPackedRoundtrip( AllPrimitiveTypes.serializer(), - AllPrimitiveTypes(0, 0L, 0, 0, 0f, 0.0, '中', false, "") + AllPrimitiveTypes(0, 0L, 0, 0, 0f, 0.0, '中', false, ""), ) } @@ -458,11 +454,11 @@ class PackedFormatTest { val list = listOf(Child(1), null, Child(3), null, Child(5)) val bytes = PackedFormat.encodeToByteArray( ListSerializer(Child.serializer().nullable), - list + list, ) val decoded = PackedFormat.decodeFromByteArray( ListSerializer(Child.serializer().nullable), - bytes + bytes, ) assertEquals(list, decoded) } @@ -471,14 +467,14 @@ class PackedFormatTest { fun `truncated bool list bitmap throws`() { val valid = PackedFormat.encodeToByteArray( ListSerializer(Boolean.serializer()), - List(16) { true } + List(16) { true }, ) val truncated = valid.copyOfRange(0, 2) assertFailsWith { PackedFormat.decodeFromByteArray( ListSerializer(Boolean.serializer()), - truncated + truncated, ) } } @@ -488,13 +484,13 @@ class PackedFormatTest { val list = List(16) { if (it % 2 == 0) "x" else null } val valid = PackedFormat.encodeToByteArray( ListSerializer(String.serializer().nullable), - list + list, ) val truncated = valid.copyOfRange(0, 2) assertFailsWith { PackedFormat.decodeFromByteArray( ListSerializer(String.serializer().nullable), - truncated + truncated, ) } } @@ -512,7 +508,7 @@ class PackedFormatTest { assertFailsWith { PackedFormat.decodeFromByteArray( NullableFieldsPayload.serializer(), - byteArrayOf() + byteArrayOf(), ) } } @@ -540,7 +536,7 @@ class PackedFormatTest { val payload = BooleanListPayload(0, List(8) { it % 2 == 0 }) val bytes = PackedFormat.encodeToByteArray( BooleanListPayload.serializer(), - payload + payload, ) assertEquals(3, bytes.size) assertPackedRoundtrip(BooleanListPayload.serializer(), payload) @@ -559,12 +555,12 @@ class PackedFormatTest { val list = listOf("a", null, "b", null, "c", null, "d", null) val bytes = PackedFormat.encodeToByteArray( ListSerializer(String.serializer().nullable), - list + list, ) assertEquals(10, bytes.size) val decoded = PackedFormat.decodeFromByteArray( ListSerializer(String.serializer().nullable), - bytes + bytes, ) assertEquals(list, decoded) } @@ -576,16 +572,16 @@ class PackedFormatTest { listOf("x"), listOf("a", null, "b"), emptyList(), - listOf(null) + listOf(null), ) for (list in lists) { val bytes = PackedFormat.encodeToByteArray( ListSerializer(String.serializer().nullable), - list + list, ) val decoded = PackedFormat.decodeFromByteArray( ListSerializer(String.serializer().nullable), - bytes + bytes, ) assertEquals(list, decoded) } @@ -595,7 +591,7 @@ class PackedFormatTest { fun `class with only nested booleans and nullables encodes to header byte only`() { val bytes = PackedFormat.encodeToByteArray( Level1.serializer(), - Level1(true, null) + Level1(true, null), ) assertEquals(1, bytes.size) assertEquals(0b00000011, bytes[0].toInt() and 0xFF) @@ -605,12 +601,12 @@ class PackedFormatTest { fun `merged header bit pattern matches field order and value`() { val bytes = PackedFormat.encodeToByteArray( Level1.serializer(), - Level1(false, Level2("", emptyList())) + Level1(false, Level2("", emptyList())), ) assertEquals(0, bytes[0].toInt() and 0xFF) assertPackedRoundtrip( Level1.serializer(), - Level1(false, Level2("", emptyList())) + Level1(false, Level2("", emptyList())), ) } @@ -658,7 +654,7 @@ class PackedFormatTest { fun `non-nullable nested class with no flags contributes no header bytes`() { val bytes = PackedFormat.encodeToByteArray( Parent.serializer(), - Parent(7, Child(42)) + Parent(7, Child(42)), ) assertEquals(2, bytes.size) assertPackedRoundtrip(Parent.serializer(), Parent(7, Child(42))) @@ -682,7 +678,7 @@ class PackedFormatTest { assertFailsWith { PackedFormat.decodeFromByteArray( DeepNested.serializer(), - byteArrayOf() + byteArrayOf(), ) } } @@ -694,13 +690,13 @@ class PackedFormatTest { Level1(false, null), Level1(false, null), Level1(false, null), - 0 + 0, ), DeepBreadth( Level1(true, null), Level1(true, null), Level1(true, null), - -1 + -1, ), DeepBreadth( Level1(true, Level2("a", emptyList())), @@ -720,15 +716,15 @@ class PackedFormatTest { val payload = ProtoAnnotatedPayload(signed = -2, unsigned = 100L) val bytes = PackedFormat.encodeToByteArray( ProtoAnnotatedPayload.serializer(), - payload + payload, ) assertEquals(2, bytes.size) assertEquals( payload, PackedFormat.decodeFromByteArray( ProtoAnnotatedPayload.serializer(), - bytes - ) + bytes, + ), ) } @@ -738,7 +734,7 @@ class PackedFormatTest { 0xF0.toByte(), 0x9F.toByte(), 0x98.toByte(), - 0x80.toByte() + 0x80.toByte(), ) val decoder = PackedDecoder(bytes) assertFailsWith { @@ -751,7 +747,7 @@ class PackedFormatTest { val classSerializer = UnannotatedPayload.serializer() val classBytes = PackedFormat.encodeToByteArray( classSerializer, - UnannotatedPayload(1, 2L) + UnannotatedPayload(1, 2L), ) val classDecoder = PackedDecoder(classBytes) @@ -763,7 +759,7 @@ class PackedFormatTest { assertEquals(1, classDecoder.decodeElementIndex(classDescriptor)) assertEquals( kotlinx.serialization.encoding.CompositeDecoder.DECODE_DONE, - classDecoder.decodeElementIndex(classDescriptor) + classDecoder.decodeElementIndex(classDescriptor), ) val listSerializer = ListSerializer(serializer()) @@ -780,7 +776,7 @@ class PackedFormatTest { assertEquals(1, listDecoder.decodeElementIndex(listDescriptor)) assertEquals( kotlinx.serialization.encoding.CompositeDecoder.DECODE_DONE, - listDecoder.decodeElementIndex(listDescriptor) + listDecoder.decodeElementIndex(listDescriptor), ) } } diff --git a/src/commonTest/kotlin/com/eignex/kencode/PackedUtilsTest.kt b/src/commonTest/kotlin/com/eignex/kencode/PackedUtilsTest.kt index a1672cc..86bc1df 100644 --- a/src/commonTest/kotlin/com/eignex/kencode/PackedUtilsTest.kt +++ b/src/commonTest/kotlin/com/eignex/kencode/PackedUtilsTest.kt @@ -200,7 +200,7 @@ class PackedUtilsTest { 127, 128, 300, - Int.MAX_VALUE + Int.MAX_VALUE, ) for (v in values) { val out = ByteOutput() @@ -211,7 +211,7 @@ class PackedUtilsTest { assertEquals( bytes.size, consumed, - "VarInt consumed mismatch for $v" + "VarInt consumed mismatch for $v", ) } } @@ -224,7 +224,7 @@ class PackedUtilsTest { 127, 128, 300, - Int.MAX_VALUE + Int.MAX_VALUE, ) for (v in values) { val out = ByteOutput() @@ -236,7 +236,7 @@ class PackedUtilsTest { assertEquals( bytes.size, consumed + 1, - "VarInt consumed mismatch for $v" + "VarInt consumed mismatch for $v", ) } } @@ -249,7 +249,7 @@ class PackedUtilsTest { 127L, 128L, 300L, - Long.MAX_VALUE + Long.MAX_VALUE, ) for (v in values) { val out = ByteOutput() @@ -260,7 +260,7 @@ class PackedUtilsTest { assertEquals( bytes.size, consumed, - "VarLong consumed mismatch for $v" + "VarLong consumed mismatch for $v", ) } } @@ -273,7 +273,7 @@ class PackedUtilsTest { 127L, 128L, 300L, - Long.MAX_VALUE + Long.MAX_VALUE, ) for (v in values) { val out = ByteOutput() @@ -285,7 +285,7 @@ class PackedUtilsTest { assertEquals( bytes.size, consumed + 1, - "VarLong consumed mismatch for $v" + "VarLong consumed mismatch for $v", ) } } @@ -336,33 +336,33 @@ class PackedUtilsTest { "Short" to { writeShort( 1102, - it + it, ) }, "Int" to { writeInt( 110258102, - it + it, ) }, "Long" to { writeLong( 1102401240912490L, - it + it, ) }, "VarInt" to { writeVarInt( 110249021, - it + it, ) }, "VarLong" to { writeVarLong( 112085102501L, - it + it, ) - } + }, ) types.forEach { (name, writer) -> diff --git a/src/commonTest/kotlin/com/eignex/kencode/TestClasses.kt b/src/commonTest/kotlin/com/eignex/kencode/TestClasses.kt index c242a96..54836ed 100644 --- a/src/commonTest/kotlin/com/eignex/kencode/TestClasses.kt +++ b/src/commonTest/kotlin/com/eignex/kencode/TestClasses.kt @@ -9,12 +9,7 @@ import kotlin.time.ExperimentalTime import kotlin.time.Instant @Serializable -data class SimpleIntsAndBooleans( - val id: Int, - val score: Int, - val active: Boolean, - val deleted: Boolean -) +data class SimpleIntsAndBooleans(val id: Int, val score: Int, val active: Boolean, val deleted: Boolean) @Serializable data class AllPrimitiveTypes( @@ -26,7 +21,7 @@ data class AllPrimitiveTypes( val doubleVal: Double, val charVal: Char, val boolVal: Boolean, - val stringVal: String + val stringVal: String, ) @Serializable @@ -39,7 +34,7 @@ data class AllPrimitiveTypesNullable( val doubleVal: Double?, val charVal: Char?, val boolVal: Boolean?, - val stringVal: String? + val stringVal: String?, ) @Serializable @@ -49,11 +44,7 @@ data class NoBooleansNoNulls(val x: Int, val y: Long, val msg: String) enum class Status { NEW, IN_PROGRESS, DONE } @Serializable -data class EnumPayload( - val id: Int, - val status: Status, - val secondaryStatus: Status? -) +data class EnumPayload(val id: Int, val status: Status, val secondaryStatus: Status?) @Serializable data class NullableFieldsPayload( @@ -61,7 +52,7 @@ data class NullableFieldsPayload( val maybeName: String?, val maybeScore: Long?, val maybeFlag: Boolean?, - val maybeList: List? + val maybeList: List?, ) @Serializable @@ -70,16 +61,11 @@ data class NullableBooleansAndNonBooleans( val flag2: Boolean, val flag3: Boolean?, val count: Int?, - val label: String + val label: String, ) @Serializable -data class StringHeavyPayload( - val a: String, - val b: String, - val c: String, - val d: String? -) +data class StringHeavyPayload(val a: String, val b: String, val c: String, val d: String?) @Serializable data class VarIntVarUIntPayload( @@ -88,32 +74,17 @@ data class VarIntVarUIntPayload( @PackedType(IntPacking.DEFAULT) val unsignedIntVar: Int, @PackedType(IntPacking.DEFAULT) val unsignedLongVar: Long, val plainInt: Int, - val plainLong: Long + val plainLong: Long, ) @Serializable -data class UIntInlinePayload( - val id: Int, - val first: UInt, - val second: UInt, - val active: Boolean -) +data class UIntInlinePayload(val id: Int, val first: UInt, val second: UInt, val active: Boolean) @Serializable -data class DurationInlinePayload( - val label: String, - val primary: Duration, - val secondary: Duration?, - val count: Int -) +data class DurationInlinePayload(val label: String, val primary: Duration, val secondary: Duration?, val count: Int) @Serializable -data class InstantInlinePayload( - val first: Instant, - val second: Instant?, - val active: Boolean, - val seq: Int -) +data class InstantInlinePayload(val first: Instant, val second: Instant?, val active: Boolean, val seq: Int) @Serializable data class Child(val value: Int) @@ -128,11 +99,7 @@ data class WithList(val id: Int, val items: List) data class Grid(val rows: List>) @Serializable -data class NestedOptionalList( - val id: Int, - val tags: List?, - val scores: List -) +data class NestedOptionalList(val id: Int, val tags: List?, val scores: List) @Serializable data class DeepNested(val name: String, val level1: Level1) @@ -144,24 +111,13 @@ data class Level1(val active: Boolean, val level2: Level2?) data class Level2(val data: String, val matrix: List>) @Serializable -data class MapHolder( - val config: Map, - val counts: Map? -) +data class MapHolder(val config: Map, val counts: Map?) @Serializable -data class ComplexMap( - val history: Map>, - val registry: Map -) +data class ComplexMap(val history: Map>, val registry: Map) @Serializable -data class MixedBag( - val id: Int, - val meta: Map?, - val children: List, - val flags: List -) +data class MixedBag(val id: Int, val meta: Map?, val children: List, val flags: List) @Serializable data class NullableMap(val data: Map) @@ -230,7 +186,7 @@ data class BooleanFlags63( val b60: Boolean, val b61: Boolean, val b62: Boolean, - val b63: Boolean + val b63: Boolean, ) { constructor(flags: BooleanArray) : this( flags[0], @@ -295,7 +251,7 @@ data class BooleanFlags63( flags[59], flags[60], flags[61], - flags[62] + flags[62], ) { require(flags.size == 63) { "Expected 63 flags, got ${flags.size}" } } @@ -366,7 +322,7 @@ data class BooleanFlags64( val b61: Boolean, val b62: Boolean, val b63: Boolean, - val b64: Boolean + val b64: Boolean, ) { constructor(flags: BooleanArray) : this( flags[0], @@ -432,7 +388,7 @@ data class BooleanFlags64( flags[60], flags[61], flags[62], - flags[63] + flags[63], ) { require(flags.size == 64) { "Expected 64 flags, got ${flags.size}" } } @@ -504,7 +460,7 @@ data class BooleanFlags65( val b62: Boolean, val b63: Boolean, val b64: Boolean, - val b65: Boolean + val b65: Boolean, ) { constructor(flags: BooleanArray) : this( flags[0], @@ -571,7 +527,7 @@ data class BooleanFlags65( flags[61], flags[62], flags[63], - flags[64] + flags[64], ) { require(flags.size == 65) { "Expected 65 flags, got ${flags.size}" } } @@ -581,16 +537,11 @@ data class BooleanFlags65( data class RecursiveTree( val name: String, val children: List = emptyList(), - val metadata: Map? = null + val metadata: Map? = null, ) @Serializable -data class DeepBreadth( - val branchA: Level1, - val branchB: Level1, - val branchC: Level1, - val rootValue: Int -) +data class DeepBreadth(val branchA: Level1, val branchB: Level1, val branchC: Level1, val rootValue: Int) @JvmInline @Serializable @@ -609,14 +560,14 @@ data class InlineHeavyPayload( val userId: UserId, val contactEmail: Email?, val tags: List, - val timestamp: Instant + val timestamp: Instant, ) @Serializable data class MultiLevelCollections( val complexMap: Map>>, val nestedLists: List>>, - val optionalDeepMap: Map?>? + val optionalDeepMap: Map?>?, ) @Serializable @@ -625,18 +576,14 @@ sealed class PolymorphicBase { data class Action(val name: String, val priority: Int) : PolymorphicBase() @Serializable - data class Notification(val message: String, val silent: Boolean) : - PolymorphicBase() + data class Notification(val message: String, val silent: Boolean) : PolymorphicBase() @Serializable object Heartbeat : PolymorphicBase() } @Serializable -data class PolymorphicContainer( - val main: PolymorphicBase, - val history: List -) +data class PolymorphicContainer(val main: PolymorphicBase, val history: List) @Serializable data class BooleanListPayload(val id: Int, val flags: List) @@ -649,5 +596,5 @@ data class InversePayload( @PackedType(IntPacking.FIXED) val fixedX: Int, @PackedType(IntPacking.FIXED) val fixedY: Long, @PackedType(IntPacking.FIXED) val fixedUX: UInt, - @PackedType(IntPacking.FIXED) val fixedUY: ULong + @PackedType(IntPacking.FIXED) val fixedUY: ULong, ) diff --git a/src/jvmTest/kotlin/com/eignex/kencode/ReadmeExamples.kt b/src/jvmTest/kotlin/com/eignex/kencode/ReadmeExamples.kt index 94ada26..3da7ed3 100644 --- a/src/jvmTest/kotlin/com/eignex/kencode/ReadmeExamples.kt +++ b/src/jvmTest/kotlin/com/eignex/kencode/ReadmeExamples.kt @@ -22,7 +22,8 @@ class ReadmeExamples { @PackedType(IntPacking.DEFAULT) val id: ULong, - @PackedType(IntPacking.SIGNED) // zig-zag encodes small negatives efficiently + // zig-zag encodes small negatives efficiently + @PackedType(IntPacking.SIGNED) val delta: Int, // these are packed into a bitset along with nullability flags @@ -32,11 +33,13 @@ class ReadmeExamples { val handledAt: Instant?, // encoded as varint ordinal - val type: PayloadType + val type: PayloadType, ) enum class PayloadType { - TYPE1, TYPE2, TYPE3 + TYPE1, + TYPE2, + TYPE3, } @Test @@ -48,7 +51,7 @@ class ReadmeExamples { sensitive = false, external = true, handledAt = null, - type = PayloadType.TYPE1 + type = PayloadType.TYPE1, ) val message = EncodedFormat.encodeToString(payload) println(message) From 89f6491bb73f70dfb85d5a3bdcd0570b2b8d18e4 Mon Sep 17 00:00:00 2001 From: Rasmus Ros Date: Thu, 21 May 2026 14:34:14 +0200 Subject: [PATCH 2/6] chore: add kbuild-platform BOM and drop macosX64 target --- build.gradle.kts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 28945d0..1b29c4d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -14,16 +14,18 @@ kotlin { wasmJs { browser(); nodejs() } wasmWasi { nodejs() } linuxX64(); linuxArm64() - macosX64(); macosArm64(); mingwX64() + macosArm64(); mingwX64() iosX64(); iosArm64(); iosSimulatorArm64() sourceSets { commonMain.dependencies { + implementation(project.dependencies.platform("com.eignex:kbuild-platform:1.2.0")) compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-core") compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-protobuf") implementation("com.ionspin.kotlin:bignum:0.3.10") } commonTest.dependencies { + implementation(project.dependencies.platform("com.eignex:kbuild-platform:1.2.0")) implementation("org.jetbrains.kotlinx:kotlinx-serialization-core") implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf") } From 7b09135605da79f98b818e342d99c3e2dccf7af6 Mon Sep 17 00:00:00 2001 From: Rasmus Ros Date: Thu, 21 May 2026 14:38:29 +0200 Subject: [PATCH 3/6] chore: bump metaspace to 1g for kotlin native compiler --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index b179c36..b6085bc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.configuration-cache=true kotlin.suppressGradlePluginWarnings=IncorrectCompileOnlyDependencyWarning -org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=512m +org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1g org.gradle.workers.max=4 From 531012239626a2e559da452ee1ccdd029cbc1766 Mon Sep 17 00:00:00 2001 From: Rasmus Ros Date: Thu, 21 May 2026 15:41:24 +0200 Subject: [PATCH 4/6] chore: bump kbuild to 1.2.1 with auto-applied platform BOM --- build.gradle.kts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 1b29c4d..1bae292 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("com.eignex.kmp") version "1.2.0" + id("com.eignex.kmp") version "1.2.1" kotlin("plugin.serialization") version "2.3.20" } @@ -19,13 +19,11 @@ kotlin { sourceSets { commonMain.dependencies { - implementation(project.dependencies.platform("com.eignex:kbuild-platform:1.2.0")) compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-core") compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-protobuf") implementation("com.ionspin.kotlin:bignum:0.3.10") } commonTest.dependencies { - implementation(project.dependencies.platform("com.eignex:kbuild-platform:1.2.0")) implementation("org.jetbrains.kotlinx:kotlinx-serialization-core") implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf") } From 97acbbfacdaf7160f3f11e4e9287d1c14b1e8a95 Mon Sep 17 00:00:00 2001 From: Rasmus Ros Date: Thu, 21 May 2026 15:52:46 +0200 Subject: [PATCH 5/6] fix: address typed-detekt findings from kbuild 1.2.1 --- src/commonMain/kotlin/com/eignex/kencode/BaseRadix.kt | 2 +- .../kotlin/com/eignex/kencode/PackedFormatTest.kt | 5 +++-- src/commonTest/kotlin/com/eignex/kencode/TestClasses.kt | 8 ++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/commonMain/kotlin/com/eignex/kencode/BaseRadix.kt b/src/commonMain/kotlin/com/eignex/kencode/BaseRadix.kt index 92fa851..e99b091 100644 --- a/src/commonMain/kotlin/com/eignex/kencode/BaseRadix.kt +++ b/src/commonMain/kotlin/com/eignex/kencode/BaseRadix.kt @@ -277,7 +277,7 @@ open class BaseRadix(private val alphabet: Alphabet, val blockSize: Int = 32) : val pad = blockSize - actualBytes.size require(pad >= 0) { "Decoded block exceeds expected block size." } - for (i in 0 until pad) output[outPos++] = 0 + repeat(pad) { output[outPos++] = 0 } actualBytes.copyInto(output, outPos) outPos += actualBytes.size diff --git a/src/commonTest/kotlin/com/eignex/kencode/PackedFormatTest.kt b/src/commonTest/kotlin/com/eignex/kencode/PackedFormatTest.kt index f0f1ed2..eb721f4 100644 --- a/src/commonTest/kotlin/com/eignex/kencode/PackedFormatTest.kt +++ b/src/commonTest/kotlin/com/eignex/kencode/PackedFormatTest.kt @@ -6,6 +6,7 @@ import kotlinx.serialization.* import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.builtins.nullable import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.encoding.CompositeDecoder import kotlinx.serialization.protobuf.ProtoIntegerType import kotlinx.serialization.protobuf.ProtoType import kotlin.test.Test @@ -758,7 +759,7 @@ class PackedFormatTest { assertEquals(0, classDecoder.decodeElementIndex(classDescriptor)) assertEquals(1, classDecoder.decodeElementIndex(classDescriptor)) assertEquals( - kotlinx.serialization.encoding.CompositeDecoder.DECODE_DONE, + CompositeDecoder.DECODE_DONE, classDecoder.decodeElementIndex(classDescriptor), ) @@ -775,7 +776,7 @@ class PackedFormatTest { assertEquals(0, listDecoder.decodeElementIndex(listDescriptor)) assertEquals(1, listDecoder.decodeElementIndex(listDescriptor)) assertEquals( - kotlinx.serialization.encoding.CompositeDecoder.DECODE_DONE, + CompositeDecoder.DECODE_DONE, listDecoder.decodeElementIndex(listDescriptor), ) } diff --git a/src/commonTest/kotlin/com/eignex/kencode/TestClasses.kt b/src/commonTest/kotlin/com/eignex/kencode/TestClasses.kt index 54836ed..e556dbd 100644 --- a/src/commonTest/kotlin/com/eignex/kencode/TestClasses.kt +++ b/src/commonTest/kotlin/com/eignex/kencode/TestClasses.kt @@ -571,15 +571,15 @@ data class MultiLevelCollections( ) @Serializable -sealed class PolymorphicBase { +sealed interface PolymorphicBase { @Serializable - data class Action(val name: String, val priority: Int) : PolymorphicBase() + data class Action(val name: String, val priority: Int) : PolymorphicBase @Serializable - data class Notification(val message: String, val silent: Boolean) : PolymorphicBase() + data class Notification(val message: String, val silent: Boolean) : PolymorphicBase @Serializable - object Heartbeat : PolymorphicBase() + object Heartbeat : PolymorphicBase } @Serializable From dfafa1767303efd7c08880e4ed329f916030f123 Mon Sep 17 00:00:00 2001 From: Rasmus Ros Date: Thu, 21 May 2026 16:00:37 +0200 Subject: [PATCH 6/6] chore: refresh yarn.lock for new kotlin version --- kotlin-js-store/yarn.lock | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock index de88066..b1ef29f 100644 --- a/kotlin-js-store/yarn.lock +++ b/kotlin-js-store/yarn.lock @@ -1103,6 +1103,11 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + is-plain-obj@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" @@ -1369,10 +1374,10 @@ mkdirp@^0.5.5: dependencies: minimist "^1.2.6" -mocha@11.7.2: - version "11.7.2" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-11.7.2.tgz#3c0079fe5cc2f8ea86d99124debcc42bb1ab22b5" - integrity sha512-lkqVJPmqqG/w5jmmFtiRvtA2jkDyNVUcefFJKb2uyX4dekk8Okgqop3cgbFiaIvj8uCRJVTP5x9dfxGyXm2jvQ== +mocha@11.7.5: + version "11.7.5" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-11.7.5.tgz#58f5bbfa5e0211ce7e5ee6128107cefc2515a627" + integrity sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig== dependencies: browser-stdout "^1.3.1" chokidar "^4.0.1" @@ -1382,6 +1387,7 @@ mocha@11.7.2: find-up "^5.0.0" glob "^10.4.5" he "^1.2.0" + is-path-inside "^3.0.3" js-yaml "^4.1.0" log-symbols "^4.1.0" minimatch "^9.0.5"