support for @Unsigned fields and fox bad error of kotlinjs x is Float glitch: it returns true for integers

This commit is contained in:
Sergey Chernov 2023-03-13 16:27:06 +01:00
parent 6bc01e9534
commit 0e8f3daf99
7 changed files with 103 additions and 30 deletions

View File

@ -25,11 +25,7 @@ interface DataSink {
} }
inline fun <reified T:Any>DataSink.writeNumber(value: T) { inline fun <reified T:Any>DataSink.writeNumber(value: T) {
when(value) { Smartint.encode(value, this)
is Float -> writeFloat(value)
is Double -> writeDouble(value)
else -> Smartint.encode(value, this)
}
} }
fun DataSink.writeI32(value: Int) { fun DataSink.writeI32(value: Int) {

View File

@ -13,7 +13,7 @@ interface DataSource {
/** /**
* Exception that implementations must throw on end of data * Exception that implementations must throw on end of data
*/ */
class EndOfData() : Exception("no more data available") class EndOfData: Exception("no more data available")
fun readByte(): Byte fun readByte(): Byte
@ -51,3 +51,4 @@ inline fun <reified T : Any> DataSource.readNumber(): T = when(typeOf<T>()) {
typeOf<Float>() -> readFloat() as T typeOf<Float>() -> readFloat() as T
else -> Smartint.decode(this) else -> Smartint.decode(this)
} }

View File

@ -17,12 +17,14 @@ import net.sergeych.bintools.*
class BipackDecoder(val input: DataSource, var elementsCount: Int = 0) : AbstractDecoder() { class BipackDecoder(val input: DataSource, var elementsCount: Int = 0) : AbstractDecoder() {
private var elementIndex = 0 private var elementIndex = 0
private var nextIsUnsigned = false
override val serializersModule: SerializersModule = EmptySerializersModule override val serializersModule: SerializersModule = EmptySerializersModule
override fun decodeBoolean(): Boolean = input.readByte().toInt() != 0 override fun decodeBoolean(): Boolean = input.readByte().toInt() != 0
override fun decodeByte(): Byte = input.readByte() override fun decodeByte(): Byte = input.readByte()
override fun decodeShort(): Short = input.readNumber() override fun decodeShort(): Short = if (nextIsUnsigned) input.readNumber<UInt>().toShort() else input.readNumber()
override fun decodeInt(): Int = input.readNumber() override fun decodeInt(): Int = if (nextIsUnsigned) input.readNumber<UInt>().toInt() else input.readNumber()
override fun decodeLong(): Long = input.readNumber() override fun decodeLong(): Long = if (nextIsUnsigned) input.readNumber<ULong>().toLong() else input.readNumber()
override fun decodeFloat(): Float = input.readFloat() override fun decodeFloat(): Float = input.readFloat()
override fun decodeDouble(): Double = input.readDouble() override fun decodeDouble(): Double = input.readDouble()
override fun decodeChar(): Char = Char(input.readNumber<UInt>().toInt()) override fun decodeChar(): Char = Char(input.readNumber<UInt>().toInt())
@ -37,6 +39,7 @@ class BipackDecoder(val input: DataSource, var elementsCount: Int = 0) : Abstrac
override fun decodeElementIndex(descriptor: SerialDescriptor): Int { override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
if (elementIndex >= elementsCount) return CompositeDecoder.DECODE_DONE if (elementIndex >= elementsCount) return CompositeDecoder.DECODE_DONE
nextIsUnsigned = descriptor.getElementAnnotations(elementIndex).any { it is Unsigned }
return elementIndex++ return elementIndex++
} }

View File

@ -14,26 +14,49 @@ class BipackEncoder(var output: DataSink) : AbstractEncoder() {
// used when CRC calculation on the fly // used when CRC calculation on the fly
private var crcSink: CRC32Sink? = null private var crcSink: CRC32Sink? = null
private var nextIsUnsigned = false
override fun encodeElement(descriptor: SerialDescriptor, index: Int): Boolean {
return super.encodeElement(descriptor, index).also {
println(">> $index: ${descriptor.getElementAnnotations(index)}")
nextIsUnsigned = descriptor.getElementAnnotations(index).any { it is Unsigned }
println("${descriptor.getElementDescriptor(index)} -> $nextIsUnsigned")
}
}
// fun isUnsigned(): Boolean {
//
// return
// }
override val serializersModule: SerializersModule = EmptySerializersModule override val serializersModule: SerializersModule = EmptySerializersModule
override fun encodeBoolean(value: Boolean) = output.writeByte(if (value) 1 else 0) override fun encodeBoolean(value: Boolean) = output.writeByte(if (value) 1 else 0)
override fun encodeByte(value: Byte) = output.writeByte(value.toInt()) override fun encodeByte(value: Byte) = output.writeByte(value.toInt())
override fun encodeShort(value: Short) = output.writeNumber(value.toInt()) override fun encodeShort(value: Short) = if( nextIsUnsigned )
override fun encodeInt(value: Int) = output.writeNumber(value) output.writeNumber(value.toUInt())
else
output.writeNumber(value.toInt())
override fun encodeInt(value: Int) {
println("EncodeInt: $value / $nextIsUnsigned")
if (nextIsUnsigned)
output.writeNumber(value.toUInt())
else
output.writeNumber(value)
}
fun encodeUInt(value: UInt) = output.writeNumber(value) fun encodeUInt(value: UInt) = output.writeNumber(value)
override fun encodeLong(value: Long) = output.writeNumber(value) override fun encodeLong(value: Long) = if( nextIsUnsigned )
override fun encodeFloat(value: Float) = output.writeNumber(value) output.writeNumber(value.toULong())
override fun encodeDouble(value: Double) = output.writeI64(value.toRawBits()) else
output.writeNumber(value)
override fun encodeFloat(value: Float) = output.writeFloat(value)
override fun encodeDouble(value: Double) = output.writeDouble(value)
override fun encodeChar(value: Char) = output.writeNumber(value.code.toUInt()) override fun encodeChar(value: Char) = output.writeNumber(value.code.toUInt())
override fun encodeString(value: String) { override fun encodeString(value: String) {
// output.writeUTF(value) // output.writeUTF(value)
writeBytes(value.encodeToByteArray()) writeBytes(value.encodeToByteArray())
} }
override fun encodeElement(descriptor: SerialDescriptor, index: Int): Boolean {
return super.encodeElement(descriptor, index)
}
fun writeBytes(value: ByteArray) { fun writeBytes(value: ByteArray) {
output.writeNumber(value.size.toUInt()) output.writeNumber(value.size.toUInt())
output.writeBytes(value) output.writeBytes(value)
@ -48,19 +71,18 @@ class BipackEncoder(var output: DataSink) : AbstractEncoder() {
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:
if( descriptor.annotations.any { it is CrcProtected }) { if (descriptor.annotations.any { it is CrcProtected }) {
crcSink = CRC32Sink(output).also { crcSink = CRC32Sink(output).also {
output = it output = it
} }
} }
// now it is safe to process anything else // now it is safe to process anything else
for( a in descriptor.annotations) { for (a in descriptor.annotations) {
if (a is Framed) { if (a is Framed) {
output.writeU32( output.writeU32(
CRC.crc32(descriptor.serialName.encodeToByteArray()) CRC.crc32(descriptor.serialName.encodeToByteArray())
) )
} } else if (a is ExtendableFormat) {
else if( a is ExtendableFormat) {
encodeUInt(descriptor.elementsCount.toUInt()) encodeUInt(descriptor.elementsCount.toUInt())
} }
} }
@ -80,16 +102,16 @@ class BipackEncoder(var output: DataSink) : AbstractEncoder() {
override fun encodeNotNullMark() = encodeBoolean(true) override fun encodeNotNullMark() = encodeBoolean(true)
companion object { companion object {
fun <T> encode(serializer: SerializationStrategy<T>, value: T,sink: DataSink) { fun <T> encode(serializer: SerializationStrategy<T>, value: T, sink: DataSink) {
val encoder = BipackEncoder(sink) val encoder = BipackEncoder(sink)
encoder.encodeSerializableValue(serializer, value) encoder.encodeSerializableValue(serializer, value)
} }
fun <T> encode(serializer: SerializationStrategy<T>, value: T): ByteArray = fun <T> encode(serializer: SerializationStrategy<T>, value: T): ByteArray =
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)
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

@ -26,9 +26,27 @@ annotation class ExtendableFormat
@SerialInfo @SerialInfo
annotation class Framed annotation class Framed
/**
* Allow to CRC-protect structures (we suppose to use it with classes only). After the
* data block its CRC32 will be written and checked. It is memory-wise: it calculates CRC
* on the fly without buffering the data. If used with [Framed] and [ExtendableFormat] the extra
* data is protected too.
*
* __Common pitfalls__. When unpacking corrupted data protected this way, the not only [InvalidFrameCRCException]
* can be thrown. Actually, most often you will see [DataSource.EndOfData] exception
*/
@SerialInfo @SerialInfo
annotation class CrcProtected annotation class CrcProtected
/**
* Allow marking data fields as being serialized as usnsigned (applyable also to signed fields lite Int, Long and
* Short, if you are shure they will not be negative). As unsigned types are not cully supported by kotlinx.serialization
* it is the conly way to tell the serialized to use more compact unsigned variable length encoding.
*/
@SerialInfo
@Target(AnnotationTarget.FIELD,AnnotationTarget.PROPERTY)
annotation class Unsigned
open class InvalidFrameException(reason: String) : Exception(reason) open class InvalidFrameException(reason: String) : Exception(reason)
class InvalidFrameHeaderException(reason: String = "Frame header does not match") : InvalidFrameException(reason) class InvalidFrameHeaderException(reason: String = "Frame header does not match") : InvalidFrameException(reason)
class InvalidFrameCRCException : InvalidFrameException("Checksum CRC32 failed") class InvalidFrameCRCException : InvalidFrameException("Checksum CRC32 failed")

View File

@ -64,4 +64,5 @@ class SmartintTest {
// } // }
} }
} }
} }

View File

@ -40,6 +40,17 @@ data class FoobarFP1(val bar: Int, val foo: Int,val other: Int = -1)
class BipackEncoderTest { class BipackEncoderTest {
@Serializable
data class FoobarSize(val i: Int)
@Test
fun testSize() {
println(BipackEncoder.encode(17).toDump())
assertEquals(1, BipackEncoder.encode(17).size)
// assertEquals(1, BipackEncoder.encode(FoobarSize(17)).size)
}
@Test @Test
fun encodeSimple() { fun encodeSimple() {
val a = Foobar1(42)//, "bum") val a = Foobar1(42)//, "bum")
@ -87,13 +98,34 @@ class BipackEncoderTest {
} }
@Serializable @Serializable
data class FBU(val u: UInt, val i: Int) data class FBU(
@Unsigned
val u: UInt,
@Unsigned
val i: Int,
val k: UInt = 3u)
@Test @Test
fun testByteArray() { fun testByteArray() {
// val z = Foobar1(42)//, "bum")
// println(BipackEncoder.encode(z).toDump())
val x = byteArrayOf(1,2,3) val x = byteArrayOf(1,2,3)
println(BipackEncoder.encode(x).toDump()) var d = BipackEncoder.encode(x)
println(BipackEncoder.encode("123").toDump()) println(d.toDump())
println(BipackEncoder.encode(FBU(3u, 3)).toDump()) assertEquals(0x0c, d[0])
// println(BipackEncoder.encode(1U).toDump())
d = BipackEncoder.encode("123")
println(d.toDump())
assertEquals(0x0c, d[0])
val f = FBU(3u, 3)
d = BipackEncoder.encode(f)
println(d.toDump())
assertEquals(0x0c, d[0])
assertEquals(0x0c, d[1])
// Signed encoding despite UInt type:
assertEquals(0x18, d[2])
assertEquals(f, BipackDecoder.decode(d))
} }
} }