Compare commits

..

No commits in common. "469e434395ab63f42c72a2c4becd662f27fd15fb" and "b8ac3e20e09098aecb7e5b96a44078544521d6e6" have entirely different histories.

8 changed files with 21 additions and 106 deletions

View File

@ -6,11 +6,9 @@ in native targets.
# Important note # Important note
Currently published version 0.4.0 for all platform is fully compatible with breaking kotlinx.datetime/kotlin.time migration as of Kotlin 2.2.21 and is __a recommended version to use__. Still there could be minor issues as: Currently published version 0.3.2 for all platform is fully compatible with breaking kotlinx.datetime/kotlin.time migration as of Kotlin 2.2.21 and is __a recommended version to use__.
- version before `0.4.0` treated unsigned ints as signed ones, as older versions of `kotlinx.serialization` did. Since `0.4.0` UInt, ULong and UShort are treated as unsigned, no more @Unsigned annotation is needed. Sorry for inconveniences, it is all caused by strange ideas of the Kotlin team.
Most likely it will not cause any problems, but be informed.
# Documentation # Documentation
@ -188,17 +186,7 @@ It adds four bytes to the serialized data.
## @Unsigned ## @Unsigned
This __field annotation__ allows storing signed integer fields (`Short`, `Int`, `Long`) in a more compact unsigned This __field annotation__ allows to store __integer fields__ of any size more compact by not saving the sign. It could be applied to both signed and unsigned integers of any size.
varint form when values are guaranteed non-negative.
Unsigned Kotlin types (`UByte`, `UShort`, `UInt`, `ULong`) are detected automatically and do not need this annotation.
## @Varint
By default Bipack uses `Smartint` for variable-length integer coding. This __field annotation__ forces classic
`Varint` codec for the annotated integer field.
It can be combined with `@Unsigned` on signed integer fields.
## @FixedSize(size) ## @FixedSize(size)
@ -228,3 +216,4 @@ class Foo(
// so: // so:
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())
~~~ ~~~

View File

@ -9,7 +9,7 @@ plugins {
} }
group = "net.sergeych" group = "net.sergeych"
version = "0.4.0" version = "0.3.2"
repositories { repositories {
google() google()
@ -62,7 +62,7 @@ kotlin {
dependencies { dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
// 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.10.0") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
api("net.sergeych:mp_stools:[1.6.3,)") api("net.sergeych:mp_stools:[1.6.3,)")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.7.1") implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.7.1")
} }

View File

@ -17,7 +17,7 @@ Bipack is a common kotlinx serializer that works pretty much like any other `kot
- [BipackEncoder] to serializes anything to bipack format. - [BipackEncoder] to serializes anything to bipack format.
- [BipackDecoder] deserializes from bipack back. - [BipackDecoder] deserializes from bipack back.
There are also special annotation to fine tune the format: [Extendable], [Framed], [CrcProtected] for classes and [Unsigned], [Varint] for integer data fields. There are also special annotation to fine tune the format: [Extendable], [Framed], [CrcProtected] for classes and [Unsigned] for integer data fields.
# Package net.sergeych.bintools # Package net.sergeych.bintools
@ -29,4 +29,4 @@ In particular, see [Varint] and [Smartint] variable-length compact integer codec
To write a code that compiles and runs, and most likely works on the To write a code that compiles and runs, and most likely works on the
JS, native, and JVM, we need some portable/compatible synchronization JS, native, and JVM, we need some portable/compatible synchronization
primitives. This package is a collection of such. primitives. This package is a collection of such.

View File

@ -2,8 +2,8 @@
package net.sergeych.bintools package net.sergeych.bintools
/** /**
* Variable-length long integer encoding. The MSB (0x80) bit of each byte flags * Variable-length long integer encoding. the MSB (0x80) bit of each byte flags
* that it is not the last one, and all necessary bits are encoded with 7-bit * that it is not the last one, and all ncecssary bits are encoded with 7-bit
* portions, LSB to MSB (big endian of sorts). * portions, LSB to MSB (big endian of sorts).
* *
* There is slower but more compact encoding variant, [Smartint] that is better when * There is slower but more compact encoding variant, [Smartint] that is better when

View File

@ -25,7 +25,6 @@ class BipackDecoder(
private var elementIndex = 0 private var elementIndex = 0
private var nextIsUnsigned = false private var nextIsUnsigned = false
private var nextIsVarint = false
private var fixedSize = -1 private var fixedSize = -1
private var fixedNumber = false private var fixedNumber = false
@ -34,9 +33,6 @@ class BipackDecoder(
override fun decodeByte(): Byte = input.readByte() override fun decodeByte(): Byte = input.readByte()
override fun decodeShort(): Short = override fun decodeShort(): Short =
if (fixedNumber) input.readI16() if (fixedNumber) input.readI16()
else if (nextIsVarint)
if (nextIsUnsigned) input.readVarUInt().toShort()
else input.readVarInt().toShort()
else if (nextIsUnsigned) else if (nextIsUnsigned)
input.readNumber<UInt>().toShort() input.readNumber<UInt>().toShort()
else else
@ -44,16 +40,10 @@ class BipackDecoder(
override fun decodeInt(): Int = override fun decodeInt(): Int =
if (fixedNumber) input.readI32() if (fixedNumber) input.readI32()
else if (nextIsVarint)
if (nextIsUnsigned) input.readVarUInt().toInt()
else input.readVarInt()
else if (nextIsUnsigned) input.readNumber<UInt>().toInt() else input.readNumber() else if (nextIsUnsigned) input.readNumber<UInt>().toInt() else input.readNumber()
override fun decodeLong(): Long = override fun decodeLong(): Long =
if (fixedNumber) input.readI64() if (fixedNumber) input.readI64()
else if (nextIsVarint)
if (nextIsUnsigned) net.sergeych.bintools.Varint.decodeUnsigned(input).toLong()
else net.sergeych.bintools.Varint.decodeSigned(input)
else if (nextIsUnsigned) input.readNumber<ULong>().toLong() else input.readNumber() else if (nextIsUnsigned) input.readNumber<ULong>().toLong() else input.readNumber()
override fun decodeFloat(): Float = input.readFloat() override fun decodeFloat(): Float = input.readFloat()
@ -73,11 +63,9 @@ class BipackDecoder(
if (elementIndex >= elementsCount) if (elementIndex >= elementsCount)
return CompositeDecoder.DECODE_DONE return CompositeDecoder.DECODE_DONE
nextIsUnsigned = false nextIsUnsigned = false
nextIsVarint = false
for (a in descriptor.getElementAnnotations(elementIndex)) { for (a in descriptor.getElementAnnotations(elementIndex)) {
when (a) { when (a) {
is Unsigned -> nextIsUnsigned = true is Unsigned -> nextIsUnsigned = true
is Varint -> nextIsVarint = true
is FixedSize -> fixedSize = a.size is FixedSize -> fixedSize = a.size
is Fixed -> fixedNumber = true is Fixed -> fixedNumber = true
} }
@ -85,12 +73,6 @@ class BipackDecoder(
return elementIndex++ return elementIndex++
} }
override fun decodeInline(descriptor: SerialDescriptor): BipackDecoder {
if (descriptor.isUnsignedInlinePrimitive())
nextIsUnsigned = true
return this
}
override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T { override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T {
return if (deserializer == serializer<Instant>()) return if (deserializer == serializer<Instant>())
Instant.fromEpochMilliseconds(decodeLong()) as T Instant.fromEpochMilliseconds(decodeLong()) as T
@ -154,9 +136,6 @@ class BipackDecoder(
@ExperimentalSerializationApi @ExperimentalSerializationApi
override fun decodeNull(): Nothing? = null override fun decodeNull(): Nothing? = null
private fun SerialDescriptor.isUnsignedInlinePrimitive(): Boolean =
isInline && serialName in setOf("kotlin.UInt", "kotlin.ULong", "kotlin.UShort", "kotlin.UByte")
companion object { companion object {
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)
@ -177,3 +156,4 @@ class BipackDecoder(
inline fun <reified T> ByteArray.decodeFromBipack() = BipackDecoder.decode<T>(this) inline fun <reified T> ByteArray.decodeFromBipack() = BipackDecoder.decode<T>(this)
@Suppress("unused") @Suppress("unused")
inline fun <reified T> UByteArray.decodeFromBipack() = BipackDecoder.decode<T>(this) inline fun <reified T> UByteArray.decodeFromBipack() = BipackDecoder.decode<T>(this)

View File

@ -13,38 +13,26 @@ import kotlin.time.Instant
class BipackEncoder(val output: DataSink) : AbstractEncoder() { class BipackEncoder(val output: DataSink) : AbstractEncoder() {
private var nextIsUnsigned = false private var nextIsUnsigned = false
private var nextIsVarint = false
private var fixedSize: Int = -1 private var fixedSize: Int = -1
private var fixedNumber: Boolean = false private var fixedNumber: Boolean = false
override fun encodeElement(descriptor: SerialDescriptor, index: Int): Boolean = override fun encodeElement(descriptor: SerialDescriptor, index: Int): Boolean =
super.encodeElement(descriptor, index).also { super.encodeElement(descriptor, index).also {
nextIsUnsigned = false nextIsUnsigned = false
nextIsVarint = false
for (a in descriptor.getElementAnnotations(index)) { for (a in descriptor.getElementAnnotations(index)) {
when (a) { when (a) {
is Unsigned -> nextIsUnsigned = true is Unsigned -> nextIsUnsigned = true
is Varint -> nextIsVarint = true
is FixedSize -> fixedSize = a.size is FixedSize -> fixedSize = a.size
is Fixed -> fixedNumber = true is Fixed -> fixedNumber = true
} }
} }
} }
override fun encodeInline(descriptor: SerialDescriptor): BipackEncoder {
if (descriptor.isUnsignedInlinePrimitive())
nextIsUnsigned = true
return this
}
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) = override fun encodeShort(value: Short) =
if (fixedNumber) output.writeI16(value) if (fixedNumber) output.writeI16(value)
else if (nextIsVarint)
if (nextIsUnsigned) output.writeVarUInt(value.toUShort().toUInt())
else output.writeVarInt(value.toInt())
else if (nextIsUnsigned) else if (nextIsUnsigned)
output.writeNumber(value.toUShort()) output.writeNumber(value.toUShort())
else else
@ -53,9 +41,6 @@ class BipackEncoder(val output: DataSink) : AbstractEncoder() {
override fun encodeInt(value: Int) = override fun encodeInt(value: Int) =
if (fixedNumber) if (fixedNumber)
output.writeI32(value) output.writeI32(value)
else if (nextIsVarint)
if (nextIsUnsigned) output.writeVarUInt(value.toUInt())
else output.writeVarInt(value)
else if (nextIsUnsigned) output.writeNumber(value.toUInt()) else if (nextIsUnsigned) output.writeNumber(value.toUInt())
else output.writeNumber(value) else output.writeNumber(value)
@ -63,9 +48,6 @@ class BipackEncoder(val output: DataSink) : AbstractEncoder() {
override fun encodeLong(value: Long) = override fun encodeLong(value: Long) =
if (fixedNumber) if (fixedNumber)
output.writeI64(value) output.writeI64(value)
else if (nextIsVarint)
if (nextIsUnsigned) net.sergeych.bintools.Varint.encodeUnsigned(value.toULong(), output)
else net.sergeych.bintools.Varint.encodeSigned(value, output)
else if (nextIsUnsigned) else if (nextIsUnsigned)
output.writeNumber(value.toULong()) output.writeNumber(value.toULong())
else else
@ -129,9 +111,6 @@ class BipackEncoder(val output: DataSink) : AbstractEncoder() {
override fun encodeNull() = encodeBoolean(false) override fun encodeNull() = encodeBoolean(false)
override fun encodeNotNullMark() = encodeBoolean(true) override fun encodeNotNullMark() = encodeBoolean(true)
private fun SerialDescriptor.isUnsignedInlinePrimitive(): Boolean =
isInline && serialName in setOf("kotlin.UInt", "kotlin.ULong", "kotlin.UShort", "kotlin.UByte")
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)

View File

@ -43,25 +43,14 @@ annotation class Framed
annotation class CrcProtected annotation class CrcProtected
/** /**
* Marks signed integer fields (`Int`, `Long`, `Short`) that should be encoded using unsigned variable-length format. * Allow marking data fields as being serialized as unsigned (applicable also to signed fields lite Int, Long and
* * Short, if you are sure they will not be negative). As unsigned types are not cully supported by `kotlinx.serialization`
* Native unsigned Kotlin types (`UInt`, `ULong`, `UShort`, `UByte`) are handled automatically by modern * it is the only way to tell the serialized to use more compact unsigned variable length encoding.
* `kotlinx.serialization` and do not require this annotation.
*/ */
@SerialInfo @SerialInfo
@Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY) @Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY)
annotation class Unsigned annotation class Unsigned
/**
* Marks integer fields that should use [net.sergeych.bintools.Varint] codec instead of default [net.sergeych.bintools.Smartint].
*
* Applicable to all integer types (`Byte`, `Short`, `Int`, `Long`, `UByte`, `UShort`, `UInt`, `ULong`).
* If used together with [Unsigned] on signed types, unsigned varint encoding is used.
*/
@SerialInfo
@Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY)
annotation class Varint
/** /**
* Fixed size collection of a given size. __Use it only with collections!__ * Fixed size collection of a given size. __Use it only with collections!__
* *
@ -113,3 +102,5 @@ class InvalidFrameHeaderException(reason: String = "Frame header does not match"
class InvalidFrameCRCException : InvalidFrameException("Checksum CRC32 failed") class InvalidFrameCRCException : InvalidFrameException("Checksum CRC32 failed")
class WrongCollectionSize(reason: String) : Exception(reason) class WrongCollectionSize(reason: String) : Exception(reason)

View File

@ -2,7 +2,6 @@ package bipack
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import net.sergeych.bintools.Varint as VarintCodec
import net.sergeych.bintools.encodeToHex import net.sergeych.bintools.encodeToHex
import net.sergeych.bintools.toDump import net.sergeych.bintools.toDump
import net.sergeych.bipack.* import net.sergeych.bipack.*
@ -127,6 +126,7 @@ class BipackEncoderTest {
@Serializable @Serializable
data class FBU( data class FBU(
@Unsigned
val u: UInt, val u: UInt,
@Unsigned @Unsigned
val i: Int, val i: Int,
@ -377,8 +377,11 @@ class BipackEncoderTest {
@Serializable @Serializable
data class VarUInts( data class VarUInts(
val b: UByte, val b: UByte,
@Unsigned
val si: UShort, val si: UShort,
@Unsigned
val i: UInt, val i: UInt,
@Unsigned
val li: ULong, val li: ULong,
) )
@ -401,33 +404,6 @@ class BipackEncoderTest {
println(yv) println(yv)
} }
@Serializable
data class VI1(@Varint val i: Int)
@Serializable
data class VI2(@Varint val i: UInt)
@Serializable
data class VI3(@Varint @Unsigned val i: Int)
@Test
fun testVarintAnnotation() {
val v1 = VI1(1234567)
val p1 = BipackEncoder.encode(v1)
assertContentEquals(VarintCodec.encodeSigned(1234567L), p1)
assertEquals(v1, BipackDecoder.decode<VI1>(p1))
val v2 = VI2(0xF10203u)
val p2 = BipackEncoder.encode(v2)
assertContentEquals(VarintCodec.encodeUnsigned(0xF10203uL), p2)
assertEquals(v2, BipackDecoder.decode<VI2>(p2))
val v3 = VI3(1234567)
val p3 = BipackEncoder.encode(v3)
assertContentEquals(VarintCodec.encodeUnsigned(1234567uL), p3)
assertEquals(v3, BipackDecoder.decode<VI3>(p3))
}
@Test @Test
fun testStrangeUnpack() { fun testStrangeUnpack() {
@Serializable @Serializable
@ -482,4 +458,4 @@ class BipackEncoderTest {
t1(1) t1(1)
t1(-1) t1(-1)
} }
} }