diff --git a/build.gradle.kts b/build.gradle.kts index 06177bd..520c162 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val serialization_version = "1.3.4" group = "net.sergeych" -version = "0.0.1-SNAPSHOT" +version = "0.0.2-SNAPSHOT" repositories { mavenCentral() diff --git a/src/commonMain/kotlin/net.sergeych.bintools/DataSink.kt b/src/commonMain/kotlin/net.sergeych.bintools/DataSink.kt index 4728c2d..d1e0536 100644 --- a/src/commonMain/kotlin/net.sergeych.bintools/DataSink.kt +++ b/src/commonMain/kotlin/net.sergeych.bintools/DataSink.kt @@ -36,6 +36,10 @@ inline fun DataSink.writeNumber(value: T) { fun DataSink.writeI32(value: Int) { writeBytes(intToBytes(value)) } + +fun DataSink.writeI16(value: Short) { + writeBytes(shortToBytes(value)) +} fun DataSink.writeU32(value: UInt) { writeBytes(uintToBytes(value)) } diff --git a/src/commonMain/kotlin/net.sergeych.bintools/DataSource.kt b/src/commonMain/kotlin/net.sergeych.bintools/DataSource.kt index 408accc..f5072fd 100644 --- a/src/commonMain/kotlin/net.sergeych.bintools/DataSource.kt +++ b/src/commonMain/kotlin/net.sergeych.bintools/DataSource.kt @@ -28,6 +28,7 @@ interface DataSource { fun readU32(): UInt = bytesToUInt(readBytes(4)) fun readI32(): Int = bytesToInt(readBytes(4)) + fun readI16(): Short = bytesToShort(readBytes(2)) fun readI64(): Long = bytesToLong(readBytes(8)) fun readDouble() = Double.fromBits(readI64()) diff --git a/src/commonMain/kotlin/net.sergeych.bintools/simple_codecs.kt b/src/commonMain/kotlin/net.sergeych.bintools/simple_codecs.kt index b4cda61..2f982a6 100644 --- a/src/commonMain/kotlin/net.sergeych.bintools/simple_codecs.kt +++ b/src/commonMain/kotlin/net.sergeych.bintools/simple_codecs.kt @@ -20,6 +20,15 @@ fun intToBytes(value: Int): ByteArray { } return result } +fun shortToBytes(value: Short): ByteArray { + var l = value.toInt() + val result = ByteArray(2) + for (i in 1 downTo 0) { + result[i] = (l and 0xFF).toByte() + l = l shr 8 + } + return result +} fun uintToBytes(value: UInt): ByteArray { var l = value @@ -52,6 +61,15 @@ fun bytesToInt(b: ByteArray): Int { return result } +fun bytesToShort(b: ByteArray): Short { + var result: Int = 0 + for (i in 0 until 2) { + result = result shl 8 + result = result or (b[i].toInt() and 0xFF) + } + return result.toShort() +} + fun bytesToUInt(b: ByteArray): UInt { var result = 0u for (i in 0 until 4) { diff --git a/src/commonMain/kotlin/net.sergeych.bipack/BipackDecoder.kt b/src/commonMain/kotlin/net.sergeych.bipack/BipackDecoder.kt index 4b21e2c..7c5a135 100644 --- a/src/commonMain/kotlin/net.sergeych.bipack/BipackDecoder.kt +++ b/src/commonMain/kotlin/net.sergeych.bipack/BipackDecoder.kt @@ -15,18 +15,26 @@ 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 */ -class BipackDecoder(val input: DataSource, var elementsCount: Int = 0,val isCollection: Boolean = false, - val hasFixedSize: Boolean = false) : AbstractDecoder() { +class BipackDecoder( + val input: DataSource, var elementsCount: Int = 0, val isCollection: Boolean = false, + val hasFixedSize: Boolean = false, +) : AbstractDecoder() { private var elementIndex = 0 private var nextIsUnsigned = false private var fixedSize = -1 + private var fixedNumber = false override val serializersModule: SerializersModule = EmptySerializersModule override fun decodeBoolean(): Boolean = input.readByte().toInt() != 0 override fun decodeByte(): Byte = input.readByte() - override fun decodeShort(): Short = if (nextIsUnsigned) input.readNumber().toShort() else input.readNumber() - override fun decodeInt(): Int = if (nextIsUnsigned) input.readNumber().toInt() else input.readNumber() + override fun decodeShort(): Short = + if( fixedNumber ) input.readI16() + else if (nextIsUnsigned) input.readNumber().toShort() else input.readNumber() + override fun decodeInt(): Int = + 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 decodeFloat(): Float = input.readFloat() override fun decodeDouble(): Double = input.readDouble() @@ -44,10 +52,11 @@ class BipackDecoder(val input: DataSource, var elementsCount: Int = 0,val isColl override fun decodeElementIndex(descriptor: SerialDescriptor): Int { if (elementIndex >= elementsCount) return CompositeDecoder.DECODE_DONE nextIsUnsigned = false - for( a in descriptor.getElementAnnotations(elementIndex)) { - when(a) { + for (a in descriptor.getElementAnnotations(elementIndex)) { + when (a) { is Unsigned -> nextIsUnsigned = true is FixedSize -> fixedSize = a.size + is Fixed -> fixedNumber = true } } return elementIndex++ @@ -65,7 +74,7 @@ class BipackDecoder(val input: DataSource, var elementsCount: Int = 0,val isColl // Note: we should read from 'source' explicitely as it might ve // CRC-calculating one, and the fields below are CRC protected too: - var count = if( fixedSize >= 0 ) fixedSize else descriptor.elementsCount + var count = if (fixedSize >= 0) fixedSize else descriptor.elementsCount for (a in descriptor.annotations) { if (a is Extendable) count = source.readVarUInt().toInt() @@ -83,7 +92,7 @@ class BipackDecoder(val input: DataSource, var elementsCount: Int = 0,val isColl } override fun decodeCollectionSize(descriptor: SerialDescriptor): Int { - return if( hasFixedSize ) + return if (hasFixedSize) elementsCount else input.readNumber().toInt() diff --git a/src/commonMain/kotlin/net.sergeych.bipack/BipackEncoder.kt b/src/commonMain/kotlin/net.sergeych.bipack/BipackEncoder.kt index 4396fa8..6f4e069 100644 --- a/src/commonMain/kotlin/net.sergeych.bipack/BipackEncoder.kt +++ b/src/commonMain/kotlin/net.sergeych.bipack/BipackEncoder.kt @@ -13,6 +13,7 @@ class BipackEncoder(val output: DataSink) : AbstractEncoder() { private var nextIsUnsigned = false private var fixedSize: Int = -1 + private var fixedNumber: Boolean = false override fun encodeElement(descriptor: SerialDescriptor, index: Int): Boolean = super.encodeElement(descriptor, index).also { @@ -21,21 +22,25 @@ class BipackEncoder(val output: DataSink) : AbstractEncoder() { when (a) { is Unsigned -> nextIsUnsigned = true is FixedSize -> fixedSize = a.size + is Fixed -> fixedNumber = true } } -// nextIsUnsigned = descriptor.getElementAnnotations(index).any { it is Unsigned } } override val serializersModule: SerializersModule = EmptySerializersModule override fun encodeBoolean(value: Boolean) = output.writeByte(if (value) 1 else 0) override fun encodeByte(value: Byte) = output.writeByte(value.toInt()) - override fun encodeShort(value: Short) = if (nextIsUnsigned) - output.writeNumber(value.toUInt()) - else - output.writeNumber(value.toInt()) + override fun encodeShort(value: Short) = + if (fixedNumber) output.writeI16(value) + else if (nextIsUnsigned) + output.writeNumber(value.toUInt()) + else + output.writeNumber(value.toInt()) override fun encodeInt(value: Int) = - if (nextIsUnsigned) output.writeNumber(value.toUInt()) + if (fixedNumber) + output.writeI32(value) + else if (nextIsUnsigned) output.writeNumber(value.toUInt()) else output.writeNumber(value) fun encodeUInt(value: UInt) = output.writeNumber(value) diff --git a/src/commonMain/kotlin/net.sergeych.bipack/annotations.kt b/src/commonMain/kotlin/net.sergeych.bipack/annotations.kt index 370a8d2..8a81048 100644 --- a/src/commonMain/kotlin/net.sergeych.bipack/annotations.kt +++ b/src/commonMain/kotlin/net.sergeych.bipack/annotations.kt @@ -58,6 +58,10 @@ annotation class Unsigned @Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY) annotation class FixedSize(val size: Int) +@SerialInfo +@Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY) +annotation class Fixed + open class InvalidFrameException(reason: String) : Exception(reason) class InvalidFrameHeaderException(reason: String = "Frame header does not match") : InvalidFrameException(reason) class InvalidFrameCRCException : InvalidFrameException("Checksum CRC32 failed") diff --git a/src/commonTest/kotlin/bipack/BipackEncoderTest.kt b/src/commonTest/kotlin/bipack/BipackEncoderTest.kt index 9c64ac7..c055801 100644 --- a/src/commonTest/kotlin/bipack/BipackEncoderTest.kt +++ b/src/commonTest/kotlin/bipack/BipackEncoderTest.kt @@ -2,6 +2,7 @@ package bipack import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import net.sergeych.bintools.encodeToHex import net.sergeych.bintools.toDump import net.sergeych.bipack.* import kotlin.experimental.xor @@ -297,4 +298,28 @@ class BipackEncoderTest { assertEquals(x, d.decodeFromBipack()) } + @Serializable + data class FI32(@Fixed val i: Int) + + @Serializable + data class FU32(@Fixed val i: UInt) + + @Serializable + data class FI16(@Fixed val i: Short) + + @Serializable + data class FU16(@Fixed val i: UShort) + + @Test + fun testFixedInt() { + val a= FI32(127) + println(BipackEncoder.encode(a).toDump()) + 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) + } } \ No newline at end of file