2 Commits
Author SHA1 Message Date
sergeych 1087a2c1c7 merge v0.2.2: kotlin 2.2.20 for all targets 2025-10-20 21:02:04 +02:00
sergeych ec91c0800d v0.1.8: kotlin 2.2.20 for all targets 2025-10-20 21:01:36 +02:00
19 changed files with 515 additions and 1505 deletions

No files matched your search

-1
View File
@@ -7,4 +7,3 @@
.kotlin .kotlin
/.gigaide/gigaide.properties /.gigaide/gigaide.properties
/java_pid40366.hprof /java_pid40366.hprof
/local.properties
+15 -15
View File
@@ -4,13 +4,22 @@ Multiplatform binary tools collection, including portable serialization of the c
many useful tools to work with binary data, like CRC family checksums, dumps, etc. It works well also in the browser and many useful tools to work with binary data, like CRC family checksums, dumps, etc. It works well also in the browser and
in native targets. in native targets.
# Important note # Recent changes
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: - 0.1.12 published on all platforms. many small additions.
- 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. - 0.1.11 added interesting collection classes: auto-sorted list with comparator, expirable cache, etc.
Most likely it will not cause any problems, but be informed. - 0.1.7 built with kotlin 2.0.20 which contains important fix in wasmJS
- 0.1.6 add many useful features, added support to wasmJS and all other platforms. Note to wasmJS: it appears to be a bug in wasm compiler so BipackDecoder could cause wasm loading problem.
- 0.1.1: added serialized KVStorage with handy implementation on JVM and JS platforms and some required synchronization
tools.
-
- 0.1.0: uses modern kotlin 1.9.*, fixes problem with singleton or empty/object serialization
The last 1.8-based version is 0.0.8. Some fixes are not yet backported to it pls leave an issue of needed.
# Documentation # Documentation
@@ -188,17 +197,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 +227,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())
~~~ ~~~
+17 -49
View File
@@ -1,33 +1,29 @@
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
plugins { plugins {
kotlin("multiplatform") version "2.3.20" kotlin("multiplatform") version "2.2.20"
kotlin("plugin.serialization") version "2.2.21" kotlin("plugin.serialization") version "2.2.20"
id("org.jetbrains.dokka") version "1.9.20" id("org.jetbrains.dokka") version "1.9.20"
id("com.android.library") version "8.7.2"
`maven-publish` `maven-publish`
} }
val serialization_version = "1.9.0"
group = "net.sergeych" group = "net.sergeych"
version = "0.4.0" version = "0.2.2"
repositories { repositories {
google()
mavenCentral() mavenCentral()
mavenLocal() mavenLocal()
maven("https://maven.universablockchain.com/") maven("https://maven.universablockchain.com/")
} }
kotlin { kotlin {
jvmToolchain(17) jvmToolchain(8)
jvm() jvm()
androidTarget() {
// Ensure Android variant is published to Maven so consumers get androidMain APIs
publishLibraryVariants("release")
}
js { js {
browser() browser()
nodejs() nodejs()
} }
macosArm64() macosArm64()
@@ -37,6 +33,7 @@ kotlin {
iosSimulatorArm64() iosSimulatorArm64()
linuxX64() linuxX64()
linuxArm64() linuxArm64()
mingwX64()
@OptIn(ExperimentalWasmDsl::class) @OptIn(ExperimentalWasmDsl::class)
wasmJs { wasmJs {
@@ -56,15 +53,15 @@ kotlin {
languageSettings.optIn("kotlinx.serialization.ExperimentalSerializationApi") languageSettings.optIn("kotlinx.serialization.ExperimentalSerializationApi")
languageSettings.optIn("kotlin.ExperimentalUnsignedTypes") languageSettings.optIn("kotlin.ExperimentalUnsignedTypes")
languageSettings.optIn("kotlin.contracts.ExperimentalContracts") languageSettings.optIn("kotlin.contracts.ExperimentalContracts")
languageSettings.optIn("kotlin.time.ExperimentalTime")
} }
val commonMain by getting { val commonMain by getting {
dependencies { dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
// 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,)") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.7.1") api("net.sergeych:mp_stools:[1.6.1,)")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.5.0")
} }
} }
val nativeMain by creating { val nativeMain by creating {
@@ -88,27 +85,19 @@ kotlin {
} }
val jvmMain by getting val jvmMain by getting
val jvmTest by getting val jvmTest by getting
val androidMain by getting {
dependencies {
// Android base64 and preferences are in the SDK; no extra deps required
}
}
val jsMain by getting { val jsMain by getting {
dependencies { dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-browser:0.5.0")
} }
} }
val jsTest by getting val jsTest by getting
// val nativeTest by getting // val nativeTest by getting
val wasmJsMain by getting { val wasmJsMain by getting {
dependencies { dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-browser-wasm-js:0.5.0") implementation("org.jetbrains.kotlinx:kotlinx-browser:0.5.0")
}
}
val wasmJsTest by getting {
dependencies {
// implementation("org.jetbrains.kotlinx:kotlinx-browser:0.3")
} }
} }
val wasmJsTest by getting
} }
} }
@@ -129,27 +118,6 @@ publishing {
} }
} }
} }
android {
namespace = "net.sergeych.bintools"
compileSdk = 34
defaultConfig {
minSdk = 21
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
// Publish only release variant of the Android target so consumers (Android apps)
// resolve the platform artifact instead of common metadata, which is required
// to access Android-only APIs like net.sergeych.bintools.AndroidKV
publishing {
singleVariant("release") {
withSourcesJar()
}
}
}
tasks.dokkaHtml.configure { tasks.dokkaHtml.configure {
outputDirectory.set(buildDir.resolve("dokka")) outputDirectory.set(buildDir.resolve("dokka"))
dokkaSourceSets { dokkaSourceSets {
+1 -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
+1 -6
View File
@@ -1,8 +1,3 @@
kotlin.code.style=official kotlin.code.style=official
kotlin.mpp.applyDefaultHierarchyTemplate=false kotlin.mpp.applyDefaultHierarchyTemplate=false
kotlin.daemon.jvmargs=-Xmx3072M
org.gradle.parallel=true
org.gradle.jvmargs=-Xmx4096M -Dfile.encoding=UTF-8
org.gradle.configuration-cache=true
org.gradle.caching=true
+457 -1201
View File
File diff suppressed because it is too large. Load diff
-18
View File
@@ -1,21 +1,3 @@
pluginManagement {
repositories {
google()
gradlePluginPortal()
mavenCentral()
mavenLocal()
}
}
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
mavenLocal()
maven("https://maven.universablockchain.com/")
}
}
rootProject.name = "mp_bintools" rootProject.name = "mp_bintools"
@@ -1,65 +0,0 @@
package net.sergeych.bintools
import android.content.Context
import android.content.SharedPreferences
import net.sergeych.bintools.AndroidKV.init
import net.sergeych.mp_tools.decodeBase64Compact
import net.sergeych.mp_tools.encodeToBase64Compact
/**
* Simple holder to provide Application [Context] for this library.
*
* Call [init] from your app (e.g., in `Application.onCreate`) before using
* [defaultNamedStorage].
*/
object AndroidKV {
@Volatile
private var appContext: Context? = null
fun init(context: Context) {
appContext = context.applicationContext
}
internal fun requireContext(): Context =
appContext ?: error("AndroidKV is not initialized. Call AndroidKV.init(applicationContext) first.")
}
/**
* Implementation of [KVStorage] backed by [SharedPreferences].
* Values are stored as base64-encoded strings to fit preferences capabilities.
*/
class SharedPreferencesKVStorage(
private val prefs: SharedPreferences,
keyPrefix: String = ""
) : KVStorage {
private val prefix = if (keyPrefix.isEmpty()) "" else "$keyPrefix:"
private fun k(key: String) = "$prefix$key"
override fun get(key: String): ByteArray? =
prefs.getString(k(key), null)?.decodeBase64Compact()
override fun set(key: String, value: ByteArray?) {
val e = prefs.edit()
val kk = k(key)
if (value == null) e.remove(kk)
else e.putString(kk, value.encodeToBase64Compact())
e.apply()
}
override val keys: Set<String>
get() {
val allKeys = prefs.all.keys
if (prefix.isEmpty()) return allKeys
return allKeys.filter { it.startsWith(prefix) }
.map { it.removePrefix(prefix) }
.toSet()
}
}
actual fun defaultNamedStorage(name: String): KVStorage {
val ctx = AndroidKV.requireContext()
val prefs = ctx.getSharedPreferences(name, Context.MODE_PRIVATE)
// We use separate preferences file per name, so prefix can be empty
return SharedPreferencesKVStorage(prefs)
}
@@ -1,17 +0,0 @@
package net.sergeych.synctools
import java.util.concurrent.locks.ReentrantLock
/**
* Android actual implementation mirrors JVM using [ReentrantLock].
*/
actual fun ProtectedOp(): ProtectedOpImplementation = object : ProtectedOpImplementation {
private val access = ReentrantLock()
override fun lock() {
access.lock()
}
override fun unlock() {
access.unlock()
}
}
@@ -1,22 +0,0 @@
package net.sergeych.synctools
@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
actual class WaitHandle {
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
private val access = Object()
actual fun await(milliseconds: Long): Boolean {
return synchronized(access) {
try {
access.wait(milliseconds)
true
} catch (_: InterruptedException) {
false
}
}
}
actual fun wakeUp() {
synchronized(access) { access.notifyAll() }
}
}
@@ -218,19 +218,7 @@ class MemoryKVStorage(copyFrom: KVStorage? = null) : KVStorage {
* - otherwise, the folder will be created in "`~/.local_storage`" parent directory * - otherwise, the folder will be created in "`~/.local_storage`" parent directory
* (which also will be created if needed). * (which also will be created if needed).
* *
* - In Android (new!) it is implemented using preferences, but initializing is needed in * - For the native platorms it is not yet implemented (but will be soon).
* main activity `onCreate`, somewhat like:
* ```kotlin
* override fun onCreate(savedInstanceState: Bundle?) {
* super.onCreate(savedInstanceState)
* // set the Context which Preferences will be used for
* // defaultNamedStorage
* net.sergeych.bintools.AndroidKV.init(this)
* }
* ```
* see also `AndroidKV` and `SharedPreferencesKVStorage` for Android
*
* - For the native platforms it is not yet implemented (but will be soon).
* *
* See [DataKVStorage] and [DataProvider] to implement a KVStorage on filesystems and like, * See [DataKVStorage] and [DataProvider] to implement a KVStorage on filesystems and like,
* and `FileDataProvider` class on JVM target. * and `FileDataProvider` class on JVM target.
@@ -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
@@ -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.KSerializer import kotlinx.serialization.KSerializer
@@ -11,7 +12,6 @@ import kotlinx.serialization.modules.EmptySerializersModule
import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.serializer import kotlinx.serialization.serializer
import net.sergeych.bintools.* import net.sergeych.bintools.*
import kotlin.time.Instant
/** /**
* 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]
@@ -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,14 +73,8 @@ 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 == Instant.serializer())
Instant.fromEpochMilliseconds(decodeLong()) as T Instant.fromEpochMilliseconds(decodeLong()) as T
else else
super.decodeSerializableValue(deserializer) super.decodeSerializableValue(deserializer)
@@ -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)
@@ -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
@@ -8,43 +9,30 @@ import kotlinx.serialization.modules.EmptySerializersModule
import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.serializer import kotlinx.serialization.serializer
import net.sergeych.bintools.* import net.sergeych.bintools.*
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)
@@ -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)
@@ -2,11 +2,11 @@ package net.sergeych.collections
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import net.sergeych.mptools.withReentrantLock import net.sergeych.mptools.withReentrantLock
import kotlin.time.Clock
import kotlin.time.Duration import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
import kotlin.time.Instant
/** /**
* MRU cache with expiration, with safe async concurrent access. * MRU cache with expiration, with safe async concurrent access.
@@ -1,8 +1,8 @@
package net.sergecyh.diwan.tools package net.sergecyh.diwan.tools
import kotlin.time.Clock import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlin.time.Duration import kotlin.time.Duration
import kotlin.time.Instant
/** /**
* Value with expiration. * Value with expiration.
@@ -1,8 +1,8 @@
package net.sergecyh.diwan.tools package net.sergecyh.diwan.tools
import kotlin.time.Clock import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlin.time.Duration import kotlin.time.Duration
import kotlin.time.Instant
/** /**
* Experimental. * Experimental.
@@ -1,8 +1,9 @@
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.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.*
@@ -12,8 +13,6 @@ import kotlin.test.Test
import kotlin.test.assertContentEquals import kotlin.test.assertContentEquals
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
import kotlin.time.Clock
import kotlin.time.Instant
@Serializable @Serializable
data class Foobar1N(val bar: Int, val foo: Int = 117) data class Foobar1N(val bar: Int, val foo: Int = 117)
@@ -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