diff --git a/README.md b/README.md index fc22732..bd3a225 100644 --- a/README.md +++ b/README.md @@ -100,4 +100,27 @@ This __field annontation__ allows to store __integer fields__ of any size more c ## @FixedSize(size) -Use it with fixed-size collections (like hashes, keys, etc) to not to keep collection size in the packed binary. It saves at least one byte. \ No newline at end of file +Use it with fixed-size collections (like hashes, keys, etc) to not to keep collection size in the packed binary. It saves at least one byte. + +## @Fixed + +Can be used with any integer type to store/restor it as is, fixed-size, big-endian: + +- Short, UShort: 2 bytes +- Int, UInt: 4 bytes +- Long, ULong: 8 bytes + +Note that without this modifier all integers are serialized into variable-length compressed format, see class [Smartint] from this library. + +Example: +~~~kotlin +@Serializable +class Foo( + @Fixed + val eightBytesLongInt: Long +) + +// so: +assertEquals("00 00 00 01 00 00 00 02", BipackEncoder.encode(Foo(0x100000002)).encodeToHex()) +~~~ + diff --git a/src/commonMain/kotlin/net.sergeych.bipack/BipackDecoder.kt b/src/commonMain/kotlin/net.sergeych.bipack/BipackDecoder.kt index 7c5a135..a36c062 100644 --- a/src/commonMain/kotlin/net.sergeych.bipack/BipackDecoder.kt +++ b/src/commonMain/kotlin/net.sergeych.bipack/BipackDecoder.kt @@ -35,7 +35,9 @@ class BipackDecoder( if (fixedNumber) input.readI32() else if (nextIsUnsigned) input.readNumber().toInt() else input.readNumber() - override fun decodeLong(): Long = if (nextIsUnsigned) input.readNumber().toLong() else input.readNumber() + override fun decodeLong(): Long = + if( fixedNumber ) input.readI64() + else if (nextIsUnsigned) input.readNumber().toLong() else input.readNumber() override fun decodeFloat(): Float = input.readFloat() override fun decodeDouble(): Double = input.readDouble() override fun decodeChar(): Char = Char(input.readNumber().toInt()) diff --git a/src/commonMain/kotlin/net.sergeych.bipack/BipackEncoder.kt b/src/commonMain/kotlin/net.sergeych.bipack/BipackEncoder.kt index 6f4e069..fedda4b 100644 --- a/src/commonMain/kotlin/net.sergeych.bipack/BipackEncoder.kt +++ b/src/commonMain/kotlin/net.sergeych.bipack/BipackEncoder.kt @@ -44,10 +44,13 @@ class BipackEncoder(val output: DataSink) : AbstractEncoder() { else output.writeNumber(value) fun encodeUInt(value: UInt) = output.writeNumber(value) - override fun encodeLong(value: Long) = if (nextIsUnsigned) - output.writeNumber(value.toULong()) - else - output.writeNumber(value) + override fun encodeLong(value: Long) = + if (fixedNumber) + output.writeI64(value) + else if (nextIsUnsigned) + output.writeNumber(value.toULong()) + else + output.writeNumber(value) override fun encodeFloat(value: Float) = output.writeFloat(value) override fun encodeDouble(value: Double) = output.writeDouble(value) diff --git a/src/commonMain/kotlin/net.sergeych.bipack/annotations.kt b/src/commonMain/kotlin/net.sergeych.bipack/annotations.kt index 8a81048..730f6af 100644 --- a/src/commonMain/kotlin/net.sergeych.bipack/annotations.kt +++ b/src/commonMain/kotlin/net.sergeych.bipack/annotations.kt @@ -48,16 +48,47 @@ annotation class CrcProtected annotation class Unsigned /** - * Use it with collection of fixed size, like hash digest, key bits and so on, by not storing collection - * size. It effectively reduced packed size to at least one byte. depending on the actual + * Fixed size collection of a given size. __Use it only with collections!__ + * + * Use it with collection of fixed size, to read/write exact number of items (for example, bytes), + * like hash digest, key bits and so on. Does not stores/loades the collection + * size what reduces packed size to at least one byte. depending on the actual * collection size. As for nowonly collection types (e.g. ByteArray, Listm etc) are supported. * Note that if the actual collection size differs from [size], [BipackEncoder] will throw - * [WrongCollectionSize] while encoding it. + * [WrongCollectionSize] while encoding it. For example: + * + * ~~~ + * @Serializable + * class Foo( + * @FixedSize(32) + * thirtyTwoBytes: ByteArray + * ) */ @SerialInfo @Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY) annotation class FixedSize(val size: Int) +/** + * Fixed-size number, big-endian. Could be used only with field of following types: + * + * - Int, UInt: 4 bytes + * - Short, UShort: 2 bytes + * - Long, ULong: 8 bytes. + * + * It should not be used with Byte or UByte as their size is always 1 byte ;) + * + * Example: + * ~~~ + * @Serializable + * class Foo( + * @Fixed + * val eightBytesLongInt: Long + * ) + * + * // so: + * assertEquals("00 00 00 01 00 00 00 02", BipackEncoder.encode(Foo(0x100000002)).encodeToHex()) + * ~~~ + */ @SerialInfo @Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY) annotation class Fixed diff --git a/src/commonTest/kotlin/bipack/BipackEncoderTest.kt b/src/commonTest/kotlin/bipack/BipackEncoderTest.kt index c055801..f5f6102 100644 --- a/src/commonTest/kotlin/bipack/BipackEncoderTest.kt +++ b/src/commonTest/kotlin/bipack/BipackEncoderTest.kt @@ -58,7 +58,7 @@ class BipackEncoderTest { @Test fun encodeSimple() { - val a = Foobar1N(1,2)//, "bum") + val a = Foobar1N(1, 2)//, "bum") println(BipackEncoder.encode(a).toDump()) assertEquals(2, BipackEncoder.encode(a).size) val b = BipackDecoder.decode(BipackEncoder.encode(a)) @@ -153,11 +153,11 @@ class BipackEncoderTest { @Serializable data class Types1( val i: Int = 7, - val f: Float = 1f/3f, - val d: Double = 1.0/3.0, + val f: Float = 1f / 3f, + val d: Double = 1.0 / 3.0, val b: Boolean = true, val s: String = "жёпа", - val ba: ByteArray = byteArrayOf(1,2,3), + val ba: ByteArray = byteArrayOf(1, 2, 3), val ch: Char = 'Ы', ) { override fun equals(other: Any?): Boolean { @@ -209,7 +209,7 @@ class BipackEncoderTest { // less significant part (it actually uses native JS number which is longer than Float) // So for the test purposes we need exact values (that packs into float exacly, like nitegers // and some decimals with finite and short binary representation): - val t1 = Types1(f=0.5f) + val t1 = Types1(f = 0.5f) val d = BipackEncoder.encode(t1) println(d.toDump()) val t2: Types1 = d.decodeFromBipack() @@ -223,14 +223,14 @@ class BipackEncoderTest { @Serializable data class Fixa( @FixedSize(5) - val x: ByteArray + val x: ByteArray, ) @Test fun textFixed() { - val x = byteArrayOf(1,2,3,4,5) + val x = byteArrayOf(1, 2, 3, 4, 5) //@Fixed(32) - val y = Fixa(byteArrayOf(1,2,3,4,5)) + val y = Fixa(byteArrayOf(1, 2, 3, 4, 5)) val d1 = BipackEncoder.encode(x) println(d1.toDump()) assertEquals(6, d1.size) @@ -241,14 +241,14 @@ class BipackEncoderTest { assertContentEquals(x, BipackDecoder.decode(d2).x) } -// @Test + // @Test @Test fun testMaps() { val t1 = mapOf("foo bar" to "f1", "bar" to "b1", "bazz" to "b3")//f=17f/7f) val d = BipackEncoder.encode(t1) println(d.toDump()) println(t1) - println(BipackDecoder.decode>(d)) + println(BipackDecoder.decode>(d)) // println(d.decodeFromBipack>()) // assertEquals(t1, d.decodeFromBipack()) } @@ -260,7 +260,7 @@ class BipackEncoderTest { val d = BipackEncoder.encode(t1) println(d.toDump()) println(t1) - println(BipackDecoder.decode>(d)) + println(BipackDecoder.decode>(d)) // println(d.decodeFromBipack>()) // assertEquals(t1, d.decodeFromBipack()) } @@ -274,7 +274,7 @@ class BipackEncoderTest { val d = BipackEncoder.encode(t1) println(d.toDump()) println(t1) - println(BipackDecoder.decode>(d)) + println(BipackDecoder.decode>(d)) // println(d.decodeFromBipack>()) // assertEquals(t1, d.decodeFromBipack()) } @@ -288,7 +288,7 @@ class BipackEncoderTest { @Serializable // @Framed @CrcProtected - data class Outer(val i1: Inner,val i2: Inner) + data class Outer(val i1: Inner, val i2: Inner) @Test fun testNestedInOuterProtected() { @@ -307,19 +307,38 @@ class BipackEncoderTest { @Serializable data class FI16(@Fixed val i: Short) + @Serializable + data class FI64(@Fixed val i: Long) + @Serializable data class FU16(@Fixed val i: UShort) + @Serializable + class Foo( + @Fixed + val eightBytesLongInt: Long, + ) @Test fun testFixedInt() { - val a= FI32(127) + val a = FI32(127) println(BipackEncoder.encode(a).toDump()) - assertContentEquals(byteArrayOf(0,0,0,0x7f), BipackEncoder.encode(a)) + assertContentEquals(byteArrayOf(0, 0, 0, 0x7f), BipackEncoder.encode(a)) assertEquals("FF FF FF FF", BipackEncoder.encode(FI32(-1)).encodeToHex()) assertEquals(-1, BipackEncoder.encode(FI32(-1)).decodeFromBipack().i) assertEquals("FF FF FF FF", BipackEncoder.encode(FU32(0xFFFFFFFFu)).encodeToHex()) assertEquals("FF 01 02 03", BipackEncoder.encode(FU32(0xFF010203u)).encodeToHex()) + assertEquals("FF 03", BipackEncoder.encode(FU16(0xFF03u)).encodeToHex()) assertEquals(0x7ffeu, BipackEncoder.encode(FU16(0x7ffeu)).decodeFromBipack().i) + assertEquals(-1, BipackEncoder.encode(FI16(-1)).decodeFromBipack().i) + assertEquals(0x7ffe, BipackEncoder.encode(FI16(0x7ffe)).decodeFromBipack().i) + + assertEquals("00 00 00 01 00 00 00 02", BipackEncoder.encode(FI64(0x100000002)).encodeToHex()) + assertEquals(0x100000002, BipackEncoder.encode(FI64(0x100000002)).decodeFromBipack().i) + + + // so: + assertEquals("00 00 00 01 00 00 00 02", BipackEncoder.encode(Foo(0x100000002)).encodeToHex()) + } } \ No newline at end of file