fixe #2 effective Instant binary representation (truncates to millis though)

This commit is contained in:
Sergey Chernov 2023-07-21 16:45:27 +01:00
parent d0f29ce06f
commit ee20bfdee7
7 changed files with 111 additions and 4 deletions

View File

@ -1,6 +1,6 @@
plugins { plugins {
kotlin("multiplatform") version "1.8.10" kotlin("multiplatform") version "1.8.20"
kotlin("plugin.serialization") version "1.8.10" kotlin("plugin.serialization") version "1.8.20"
id("org.jetbrains.dokka") version "1.6.0" id("org.jetbrains.dokka") version "1.6.0"
`maven-publish` `maven-publish`
} }
@ -8,7 +8,7 @@ plugins {
val serialization_version = "1.3.4" val serialization_version = "1.3.4"
group = "net.sergeych" group = "net.sergeych"
version = "0.0.2-SNAPSHOT" version = "0.0.3-SNAPSHOT"
repositories { repositories {
mavenCentral() mavenCentral()
@ -68,6 +68,7 @@ kotlin {
// this is actually a bug: we need only the core, but bare core causes strange errors // 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") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
// api("net.sergeych:mp_stools:[1.3.3,)") // api("net.sergeych:mp_stools:[1.3.3,)")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
} }
} }
val commonTest by getting { val commonTest by getting {

View File

@ -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 <T>pack(type: KType, payload: T): ByteArray
fun <T>unpack(type: KType,packed: ByteArray): T
}
inline fun <reified T>MotherPacker.pack(payload: T) = pack(typeOf<T>(), payload)
inline fun <reified T>MotherPacker.unpack(packed: ByteArray) = unpack<T>(typeOf<T>(), packed)
class JsonPacker : MotherPacker {
override fun <T> pack(type: KType, payload: T): ByteArray {
return Json.encodeToString(serializer(type), payload).encodeToByteArray()
}
override fun <T> unpack(type: KType, packed: ByteArray): T {
return Json.decodeFromString<T>(
serializer(type) as KSerializer<T>,
packed.decodeToString()
)
}
}

View File

@ -1,5 +1,6 @@
package net.sergeych.bipack package net.sergeych.bipack
import kotlinx.datetime.Instant
import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.descriptors.SerialDescriptor 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] * 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 * excpetion. Specific frames when used can throw [InvalidFrameException] and its derivatives.e
*/ */
@Suppress("UNCHECKED_CAST")
class BipackDecoder( class BipackDecoder(
val input: DataSource, var elementsCount: Int = 0, val isCollection: Boolean = false, val input: DataSource, var elementsCount: Int = 0, val isCollection: Boolean = false,
val hasFixedSize: Boolean = false, val hasFixedSize: Boolean = false,
@ -64,12 +66,18 @@ class BipackDecoder(
return elementIndex++ return elementIndex++
} }
override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T {
return if( deserializer == Instant.serializer() )
Instant.fromEpochMilliseconds(decodeLong()) as T
else
super.decodeSerializableValue(deserializer)
}
override fun decodeSequentially(): Boolean = isCollection override fun decodeSequentially(): Boolean = isCollection
override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder { override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder {
val isCollection = descriptor.kind == StructureKind.LIST || descriptor.kind == StructureKind.MAP 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) CRC32Source(input)
else else
input input
@ -119,6 +127,7 @@ class BipackDecoder(
fun <T> decode(source: DataSource, deserializer: DeserializationStrategy<T>): T = fun <T> decode(source: DataSource, deserializer: DeserializationStrategy<T>): T =
BipackDecoder(source).decodeSerializableValue(deserializer) BipackDecoder(source).decodeSerializableValue(deserializer)
@Suppress("unused")
inline fun <reified T> decode(source: DataSource): T = decode(source, serializer()) inline fun <reified T> decode(source: DataSource): T = decode(source, serializer())
inline fun <reified T> decode(source: ByteArray): T = inline fun <reified T> decode(source: ByteArray): T =
decode(source.toDataSource(), serializer()) decode(source.toDataSource(), serializer())

View File

@ -1,5 +1,6 @@
package net.sergeych.bipack package net.sergeych.bipack
import kotlinx.datetime.Instant
import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.AbstractEncoder import kotlinx.serialization.encoding.AbstractEncoder
@ -76,6 +77,11 @@ class BipackEncoder(val output: DataSink) : AbstractEncoder() {
return this return this
} }
override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) {
if (value is Instant) encodeLong(value.toEpochMilliseconds())
else super.encodeSerializableValue(serializer, value)
}
override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder { override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder {
// frame protection should start before anything else: // frame protection should start before anything else:
val sink = if (descriptor.annotations.any { it is CrcProtected }) 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() ArrayDataSink().also { encode(serializer, value, it) }.toByteArray()
inline fun <reified T> encode(value: T) = encode(serializer(), value) inline fun <reified T> encode(value: T) = encode(serializer(), value)
@Suppress("unused")
inline fun <reified T> encode(value: T, sink: DataSink) = encode(serializer(), value, sink) inline fun <reified T> encode(value: T, sink: DataSink) = encode(serializer(), value, sink)
} }

View File

@ -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 <T> pack(type: KType, payload: T): ByteArray {
return BipackEncoder.encode(serializer(type), payload)
}
override fun <T> unpack(type: KType, packed: ByteArray): T {
return BipackDecoder.decode<T>(packed.toDataSource(),
serializer(type) as KSerializer<T>)
}
}

View File

@ -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<FB1>("""{"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<FB1?>("null".encodeToByteArray()))
}
}

View File

@ -1,5 +1,7 @@
package bipack package bipack
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import net.sergeych.bintools.encodeToHex 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()) 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<Instant>(BipackEncoder.encode(x))
assertEquals(x.toEpochMilliseconds(), y.toEpochMilliseconds())
}
} }