diff --git a/build.gradle.kts b/build.gradle.kts index 520c162..d76d14f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,6 @@ plugins { - kotlin("multiplatform") version "1.8.10" - kotlin("plugin.serialization") version "1.8.10" + kotlin("multiplatform") version "1.8.20" + kotlin("plugin.serialization") version "1.8.20" id("org.jetbrains.dokka") version "1.6.0" `maven-publish` } @@ -8,7 +8,7 @@ plugins { val serialization_version = "1.3.4" group = "net.sergeych" -version = "0.0.2-SNAPSHOT" +version = "0.0.3-SNAPSHOT" repositories { mavenCentral() @@ -68,6 +68,7 @@ kotlin { // this is actually a bug: we need only the core, but bare core causes strange errors implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") // api("net.sergeych:mp_stools:[1.3.3,)") + implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0") } } val commonTest by getting { diff --git a/src/commonMain/kotlin/net.sergeych.bintools/MotherPack.kt b/src/commonMain/kotlin/net.sergeych.bintools/MotherPack.kt new file mode 100644 index 0000000..4b463a6 --- /dev/null +++ b/src/commonMain/kotlin/net.sergeych.bintools/MotherPack.kt @@ -0,0 +1,34 @@ +package net.sergeych.bintools + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.json.Json +import kotlinx.serialization.serializer +import kotlin.reflect.KType +import kotlin.reflect.typeOf + +/** + * Experimental interface for packing and unpacking binary formats. + * Initial support intended for BiPack and BOSS to make it fast replaceable. + * Also, JSON text version with binary converter is presented by default. + */ +interface MotherPacker { + fun pack(type: KType, payload: T): ByteArray + fun unpack(type: KType,packed: ByteArray): T +} + +inline fun MotherPacker.pack(payload: T) = pack(typeOf(), payload) +inline fun MotherPacker.unpack(packed: ByteArray) = unpack(typeOf(), packed) + +class JsonPacker : MotherPacker { + override fun pack(type: KType, payload: T): ByteArray { + return Json.encodeToString(serializer(type), payload).encodeToByteArray() + } + + override fun unpack(type: KType, packed: ByteArray): T { + return Json.decodeFromString( + serializer(type) as KSerializer, + packed.decodeToString() + ) + } +} + diff --git a/src/commonMain/kotlin/net.sergeych.bipack/BipackDecoder.kt b/src/commonMain/kotlin/net.sergeych.bipack/BipackDecoder.kt index a36c062..305394e 100644 --- a/src/commonMain/kotlin/net.sergeych.bipack/BipackDecoder.kt +++ b/src/commonMain/kotlin/net.sergeych.bipack/BipackDecoder.kt @@ -1,5 +1,6 @@ package net.sergeych.bipack +import kotlinx.datetime.Instant import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.descriptors.SerialDescriptor @@ -15,6 +16,7 @@ import net.sergeych.bintools.* * Decode BiPack format. Note that it relies on [DataSource] so can throw [DataSource.EndOfData] * excpetion. Specific frames when used can throw [InvalidFrameException] and its derivatives.e */ +@Suppress("UNCHECKED_CAST") class BipackDecoder( val input: DataSource, var elementsCount: Int = 0, val isCollection: Boolean = false, val hasFixedSize: Boolean = false, @@ -64,12 +66,18 @@ class BipackDecoder( return elementIndex++ } + override fun decodeSerializableValue(deserializer: DeserializationStrategy): T { + return if( deserializer == Instant.serializer() ) + Instant.fromEpochMilliseconds(decodeLong()) as T + else + super.decodeSerializableValue(deserializer) + } override fun decodeSequentially(): Boolean = isCollection override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder { val isCollection = descriptor.kind == StructureKind.LIST || descriptor.kind == StructureKind.MAP - var source = if (descriptor.annotations.any { it is CrcProtected }) + val source = if (descriptor.annotations.any { it is CrcProtected }) CRC32Source(input) else input @@ -119,6 +127,7 @@ class BipackDecoder( fun decode(source: DataSource, deserializer: DeserializationStrategy): T = BipackDecoder(source).decodeSerializableValue(deserializer) + @Suppress("unused") inline fun decode(source: DataSource): T = decode(source, serializer()) inline fun decode(source: ByteArray): T = decode(source.toDataSource(), serializer()) diff --git a/src/commonMain/kotlin/net.sergeych.bipack/BipackEncoder.kt b/src/commonMain/kotlin/net.sergeych.bipack/BipackEncoder.kt index fedda4b..5e0f8eb 100644 --- a/src/commonMain/kotlin/net.sergeych.bipack/BipackEncoder.kt +++ b/src/commonMain/kotlin/net.sergeych.bipack/BipackEncoder.kt @@ -1,5 +1,6 @@ package net.sergeych.bipack +import kotlinx.datetime.Instant import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.AbstractEncoder @@ -76,6 +77,11 @@ class BipackEncoder(val output: DataSink) : AbstractEncoder() { return this } + override fun encodeSerializableValue(serializer: SerializationStrategy, value: T) { + if (value is Instant) encodeLong(value.toEpochMilliseconds()) + else super.encodeSerializableValue(serializer, value) + } + override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder { // frame protection should start before anything else: val sink = if (descriptor.annotations.any { it is CrcProtected }) @@ -115,6 +121,7 @@ class BipackEncoder(val output: DataSink) : AbstractEncoder() { ArrayDataSink().also { encode(serializer, value, it) }.toByteArray() inline fun encode(value: T) = encode(serializer(), value) + @Suppress("unused") inline fun encode(value: T, sink: DataSink) = encode(serializer(), value, sink) } diff --git a/src/commonMain/kotlin/net.sergeych.bipack/MotherBipack.kt b/src/commonMain/kotlin/net.sergeych.bipack/MotherBipack.kt new file mode 100644 index 0000000..3714699 --- /dev/null +++ b/src/commonMain/kotlin/net.sergeych.bipack/MotherBipack.kt @@ -0,0 +1,18 @@ +package net.sergeych.bipack + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.serializer +import net.sergeych.bintools.MotherPacker +import net.sergeych.bintools.toDataSource +import kotlin.reflect.KType + +class MotherBipack : MotherPacker { + override fun pack(type: KType, payload: T): ByteArray { + return BipackEncoder.encode(serializer(type), payload) + } + + override fun unpack(type: KType, packed: ByteArray): T { + return BipackDecoder.decode(packed.toDataSource(), + serializer(type) as KSerializer) + } +} \ No newline at end of file diff --git a/src/commonTest/kotlin/bintools/JsonPackerTest.kt b/src/commonTest/kotlin/bintools/JsonPackerTest.kt new file mode 100644 index 0000000..732cf3a --- /dev/null +++ b/src/commonTest/kotlin/bintools/JsonPackerTest.kt @@ -0,0 +1,28 @@ +package bintools + +import kotlinx.serialization.Serializable +import net.sergeych.bintools.JsonPacker +import net.sergeych.bintools.pack +import net.sergeych.bintools.unpack +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class JsonPackerTest { + @Serializable + data class FB1(val foo: Int,val bar: String) + @Test + fun testPackUnpack() { + val mp = JsonPacker() + println(mp.pack(mapOf("foo" to 42)).decodeToString()) + assertEquals("""{"foo":42}""", mp.pack(mapOf("foo" to 42)).decodeToString()) + val x = mp.unpack("""{"foo":42, "bar": "foo"}""".encodeToByteArray()) + println(x) + assertEquals(42, x.foo) + assertEquals("foo", x.bar) + + var nx: FB1? = null + println(mp.pack(nx).decodeToString()) + assertNull(mp.unpack("null".encodeToByteArray())) + } +} \ No newline at end of file diff --git a/src/commonTest/kotlin/bipack/BipackEncoderTest.kt b/src/commonTest/kotlin/bipack/BipackEncoderTest.kt index f5f6102..a01f625 100644 --- a/src/commonTest/kotlin/bipack/BipackEncoderTest.kt +++ b/src/commonTest/kotlin/bipack/BipackEncoderTest.kt @@ -1,5 +1,7 @@ package bipack +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import net.sergeych.bintools.encodeToHex @@ -341,4 +343,12 @@ class BipackEncoderTest { assertEquals("00 00 00 01 00 00 00 02", BipackEncoder.encode(Foo(0x100000002)).encodeToHex()) } + + @Test + fun testInstant() { + val x = Clock.System.now() +// println( BipackEncoder.encode(x).toDump() ) + val y = BipackDecoder.decode(BipackEncoder.encode(x)) + assertEquals(x.toEpochMilliseconds(), y.toEpochMilliseconds()) + } } \ No newline at end of file