Skip to content
Merged
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
14 changes: 7 additions & 7 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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.1"
kotlin("plugin.serialization") version "2.3.20"
}

eignexPublish {
Expand All @@ -14,18 +14,18 @@ kotlin {
wasmJs { browser(); nodejs() }
wasmWasi { nodejs() }
linuxX64(); linuxArm64()
macosX64(); macosArm64(); mingwX64()
macosArm64(); mingwX64()
iosX64(); iosArm64(); iosSimulatorArm64()

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")
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -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
14 changes: 10 additions & 4 deletions kotlin-js-store/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand All @@ -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"
Expand Down
9 changes: 3 additions & 6 deletions src/commonMain/kotlin/com/eignex/kencode/Annotations.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ enum class IntPacking {
SIGNED,

/** Fixed-width. 4 bytes for `Int`, 8 bytes for `Long`. */
FIXED
FIXED,
}

/**
Expand All @@ -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) {
Expand All @@ -41,10 +41,7 @@ private fun KxProtoIntegerType.toIntPacking(): IntPacking = when (this) {
KxProtoIntegerType.FIXED -> IntPacking.FIXED
}

internal fun resolveIntEncoding(
anns: List<Annotation>,
config: PackedConfiguration
): IntPacking {
internal fun resolveIntEncoding(anns: List<Annotation>, config: PackedConfiguration): IntPacking {
anns.filterIsInstance<PackedType>().firstOrNull()?.let { return it.type }
try {
anns.filterIsInstance<KxProtoType>().firstOrNull()
Expand Down
6 changes: 1 addition & 5 deletions src/commonMain/kotlin/com/eignex/kencode/Base64.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
}
Expand Down
6 changes: 1 addition & 5 deletions src/commonMain/kotlin/com/eignex/kencode/Base85.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
}
Expand Down
27 changes: 11 additions & 16 deletions src/commonMain/kotlin/com/eignex/kencode/BaseRadix.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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." }
Expand All @@ -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)

Expand All @@ -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 ->
Expand Down Expand Up @@ -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
Expand All @@ -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)) {
Expand Down Expand Up @@ -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
Expand All @@ -282,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

Expand All @@ -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
Expand Down
6 changes: 1 addition & 5 deletions src/commonMain/kotlin/com/eignex/kencode/ByteEncoding.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
8 changes: 4 additions & 4 deletions src/commonMain/kotlin/com/eignex/kencode/Crc.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand All @@ -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()
Expand All @@ -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()
Expand Down
20 changes: 6 additions & 14 deletions src/commonMain/kotlin/com/eignex/kencode/EncodedFormat.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 <T> encodeToString(
serializer: SerializationStrategy<T>,
value: T
): String {
override fun <T> encodeToString(serializer: SerializationStrategy<T>, value: T): String {
val bytes =
configuration.binaryFormat.encodeToByteArray(serializer, value)
val payload = configuration.transform?.encode(bytes) ?: bytes
Expand All @@ -77,15 +72,12 @@ open class EncodedFormat(
*
* @throws IllegalArgumentException if the transform's decode step fails (e.g. checksum mismatch).
*/
override fun <T> decodeFromString(
deserializer: DeserializationStrategy<T>,
string: String
): T {
override fun <T> decodeFromString(deserializer: DeserializationStrategy<T>, string: String): T {
val input = configuration.codec.decode(string)
val bytes = configuration.transform?.decode(input) ?: input
return configuration.binaryFormat.decodeFromByteArray(
deserializer,
bytes
bytes,
)
}
}
Expand Down Expand Up @@ -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
Expand All @@ -142,6 +134,6 @@ fun EncodedFormat(
builder.codec,
builder.transform,
builder.binaryFormat,
)
),
)
}
24 changes: 8 additions & 16 deletions src/commonMain/kotlin/com/eignex/kencode/PackedContext.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand Down
Loading
Loading