crypto2 as a separate library

This commit is contained in:
Sergey Chernov 2023-11-23 01:01:44 +03:00
parent 67c0009b5b
commit ae3af68dab
23 changed files with 13 additions and 730 deletions

View File

@ -13,6 +13,7 @@ repositories {
mavenCentral()
mavenLocal()
maven("https://maven.universablockchain.com/")
maven("https://gitea.sergeych.net/api/packages/SergeychWorks/maven")
}
kotlin {
@ -67,6 +68,7 @@ kotlin {
api("net.sergeych:mp_bintools:0.0.6-SNAPSHOT")
api("net.sergeych:mp_stools:1.4.1")
api("net.sergeych:crypto2:0.1.1-SNAPSHOT")
}
}
val commonTest by getting {

View File

@ -1,27 +0,0 @@
package net.sergeych.crypto2
import com.ionspin.kotlin.crypto.LibsodiumInitializer
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
private var isReady = false
private val readyAccess = Mutex()
/**
* Library initialization: should be called before all other calls.
* It is safe and with little performance penalty to call it multiple times.
*/
suspend fun initCrypto() {
// faster to check with no lock
if( !isReady) {
readyAccess.withLock {
// recheck with lock, it could be ready by now
if( !isReady ) {
LibsodiumInitializer.initialize()
isReady = true
}
}
}
}

View File

@ -1,11 +0,0 @@
package net.sergeych.crypto2
import kotlinx.serialization.Serializable
@Serializable
class Seal(
val publicKey: SigningKey.Public,
val signature: UByteArray
) {
inline fun verify(message: UByteArray) = publicKey.verify(signature, message)
}

View File

@ -1,65 +0,0 @@
package net.sergeych.crypto2
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
/**
* Multi-signed data box. Use [SignedBox.invoke] to easily create
* instances and [SignedBox.plus] to add more signatures (signing keys), and
* [SignedBox.contains] to check for a specific key signature presence.
*
* It is serializable and checks integrity on deserialization. If any of seals does not
* match the signed [message], it throws [IllegalSignatureException] _on deserialization_.
* E.g., if you have it deserialized, it is ok, check it contains all needed keys among
* signers.
*
* __The main constructor is used for deserializing only__. Don't use it directly unless you
* know what you are doing as it may be dangerous.Use one of the above to create or change it.
*/
@Serializable
class SignedBox(
val message: UByteArray,
private val seals: List<Seal>,
@Transient
private val checkOnInit: Boolean = true
) {
/**
* If this instance is not signed by a given key, return new instance signed also by this
* key, or return unchanged (same) object if it is already signed by this key; you
* _can't assume it always returns a copied object!_
*/
operator fun plus(key: SigningKey.Secret): SignedBox =
if (key.publicKey in this) this
else SignedBox(message, seals + key.seal(message), false)
/**
* Check that it is signed with a specified key.
*/
operator fun contains(publicKey: SigningKey.Public): Boolean {
return seals.any { it.publicKey == publicKey }
}
init {
if (seals.isEmpty()) throw IllegalArgumentException("there should be at least one seal")
if (checkOnInit) {
if (!seals.all { it.verify(message) }) throw IllegalSignatureException()
}
}
companion object {
/**
* Create a new instance with a specific data sealed by one or more
* keys. At least one key is required to disallow providing not-signed
* instances, e.g. [SignedBox] is guaranteed to be properly sealed when
* successfully instantiated.
*
* @param data a message to sign
* @param keys a list of keys to sign with, should be at least one key.
* @throws IllegalArgumentException if keys are not specified.
*/
operator fun invoke(data: UByteArray, vararg keys: SigningKey.Secret): SignedBox =
SignedBox(data, keys.map { it.seal(data) }, false)
}
}

View File

@ -1,77 +0,0 @@
package net.sergeych.crypto2
import com.ionspin.kotlin.crypto.signature.InvalidSignatureException
import com.ionspin.kotlin.crypto.signature.Signature
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.sergeych.crypto2.SigningKey.Secret
/**
* Keys in general: public, secret and later symmetric too.
* Keys could be compared to each other for equality and used
* as a Map keys (not sure about js).
*
* Use [Secret.pair] to create new keys.
*/
@Serializable
sealed class SigningKey {
abstract val packed: UByteArray
override fun equals(other: Any?): Boolean {
return other is SigningKey && other.packed contentEquals packed
}
override fun hashCode(): Int {
return packed.contentHashCode()
}
override fun toString(): String = packed.encodeToBase64Url()
/**
* Public key to verify signatures only
*/
@Serializable
@SerialName("p")
class Public(override val packed: UByteArray) : SigningKey() {
/**
* Verify the signature and return true if it is correct.
*/
fun verify(signature: UByteArray, message: UByteArray): Boolean = try {
Signature.verifyDetached(signature, message, packed)
true
} catch (_: InvalidSignatureException) {
false
}
override fun toString(): String = "Pub:${super.toString()}"
}
/**
* Secret key to sign only
*/
@Serializable
@SerialName("s")
class Secret(override val packed: UByteArray) : SigningKey() {
val publicKey: Public by lazy {
Public(Signature.ed25519SkToPk(packed))
}
fun sign(message: UByteArray): UByteArray = Signature.detached(message, packed)
fun seal(message: UByteArray): Seal = Seal(this.publicKey, sign(message))
override fun toString(): String = "Sct:${super.toString()}"
companion object {
data class Pair(val signing: Secret, val aPublic: Public)
fun pair(): Pair {
val p = Signature.keypair()
return Pair(Secret(p.secretKey), Public(p.publicKey))
}
}
}
}
class IllegalSignatureException: RuntimeException("signed data is tampered or signature is corrupted")

View File

@ -1,7 +0,0 @@
package net.sergeych.crypto2
import net.sergeych.bintools.CRC
fun isValidContrail(data: UByteArray): Boolean = CRC.crc8(data.copyOfRange(1, data.size)) == data[0]
fun createContrail(data: UByteArray): UByteArray = ubyteArrayOf(CRC.crc8(data)) + data

View File

@ -1,111 +0,0 @@
@file:Suppress("unused")
package net.sergeych.crypto2
import com.ionspin.kotlin.crypto.secretbox.SecretBox
import com.ionspin.kotlin.crypto.secretbox.crypto_secretbox_NONCEBYTES
import com.ionspin.kotlin.crypto.util.LibsodiumRandom
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.serialization.Serializable
import net.sergeych.bintools.toDataSource
import net.sergeych.bipack.BipackDecoder
import net.sergeych.bipack.BipackEncoder
class DecryptionFailedException : RuntimeException("can't encrypt: wrong key or tampered message")
@Serializable
data class WithNonce(
val cipherData: UByteArray,
val nonce: UByteArray,
)
@Serializable
data class WithFill(
val data: UByteArray,
val safetyFill: UByteArray? = null
) {
constructor(data: UByteArray, fillSize: Int) : this(data, randomBytes(fillSize))
}
suspend fun readVarUnsigned(input: ReceiveChannel<UByte>): UInt {
var result = 0u
var cnt = 0
while(true) {
val b = input.receive().toUInt()
result = (result shl 7) or (b and 0x7fu)
if( (b and 0x80u) != 0u ) {
return result
}
if( ++cnt > 5 ) throw IllegalArgumentException("overflow while decoding varuint")
}
}
fun encodeVarUnsigned(value: UInt): UByteArray {
val result = mutableListOf<UByte>()
var rest = value
do {
val mask = if( rest <= 0x7fu ) 0x80u else 0u
result.add( (mask or (rest and 0x7fu)).toUByte() )
rest = rest shr 7
} while(rest != 0u)
return result.toUByteArray()
}
fun randomBytes(n: Int): UByteArray = if (n > 0) LibsodiumRandom.buf(n) else ubyteArrayOf()
fun randomBytes(n: UInt): UByteArray = if (n > 0u) LibsodiumRandom.buf(n.toInt()) else ubyteArrayOf()
/**
* Uniform random in `0 ..< max` range
*/
fun randomUInt(max: UInt) = LibsodiumRandom.uniform(max)
fun randomUInt(max: Int) = LibsodiumRandom.uniform(max.toUInt())
fun <T: Comparable<T>>T.limit(range: ClosedRange<T>) = when {
this < range.start -> range.start
this > range.endInclusive -> range.endInclusive
else -> this
}
fun <T: Comparable<T>>T.limitMax(max: T) = if( this < max ) this else max
fun <T: Comparable<T>>T.limitMin(min: T) = if( this > min ) this else min
fun randomNonce(): UByteArray = randomBytes(crypto_secretbox_NONCEBYTES)
/**
* Secret-key encrypt with authentication.
* Generates random nonce and add some random fill to protect
* against some analysis attacks. Nonce is included in the result. To be
* used with [decrypt].
* @param secretKey a _secret_ key, see [SecretBox.keygen()] or like.
* @param plain data to encrypt
* @param fillSize number of random fill data to add. Use random value or default.
*/
fun encrypt(
secretKey: UByteArray,
plain: UByteArray,
fillSize: Int = randomUInt((plain.size * 3 / 10).limitMin(3)).toInt()
): UByteArray {
val filled = BipackEncoder.encode(WithFill(plain, fillSize))
val nonce = randomNonce()
val encrypted = SecretBox.easy(filled.toUByteArray(), nonce, secretKey)
return BipackEncoder.encode(WithNonce(encrypted, nonce)).toUByteArray()
}
/**
* Decrypt a secret-key-based message, normally encrypted with [encrypt].
* @throws DecryptionFailedException if the key is wrong or a message is tampered with (MAC
* check failed).
*/
fun decrypt(secretKey: UByteArray, cipher: UByteArray): UByteArray {
val wn: WithNonce = BipackDecoder.decode(cipher.toDataSource())
try {
return BipackDecoder.decode<WithFill>(
SecretBox.openEasy(wn.cipherData, wn.nonce, secretKey).toDataSource()
).data
}
catch(_: com.ionspin.kotlin.crypto.secretbox.SecretBoxCorruptedOrTamperedDataExceptionOrInvalidKey) {
throw DecryptionFailedException()
}
}

View File

@ -1,8 +0,0 @@
package net.sergeych.crypto2
import net.sergeych.bintools.toDump
import net.sergeych.mp_tools.encodeToBase64Url
fun UByteArray.toDump(wide: Boolean = false) = toByteArray().toDump(wide)
fun UByteArray.encodeToBase64Url(): String = toByteArray().encodeToBase64Url()

View File

@ -1,9 +0,0 @@
package net.sergeych.tools
class AtomicCounter(initialValue: Long = 0) {
private val op = ProtectedOp()
var value: Long = initialValue
private set
fun incrementAndGet(): Long = op { ++value }
}

View File

@ -1,21 +0,0 @@
package net.sergeych.tools
/**
* Multiplatform interface to perform a regular (not suspend) operation
* protected by a platform mutex (where necessary). Get real implementation
* with [ProtectedOp]
*/
interface ProtectedOpImplementation {
/**
* Call [f] iin mutually exclusive mode, it means that only one invocation
* can be active at a time, all the rest are waiting until the current operation
* will finish.
*/
operator fun <T>invoke(f: ()->T): T
}
/**
* Get the platform-depended implementation of a mutex-protected operation.
*/
expect fun ProtectedOp(): ProtectedOpImplementation

View File

@ -1,20 +0,0 @@
package net.sergeych.tools
import kotlinx.coroutines.cancel
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
/**
* suspend until the flow produces the value to which the
* predicate returns true
*/
suspend fun <T>Flow<T>.waitFor(predicate: (T)->Boolean) {
coroutineScope {
launch {
collect {
if( predicate(it) ) cancel()
}
}
}
}

View File

@ -1,12 +0,0 @@
package net.sergeych.utools
/**
* Scan the collection and return the first non-null result of the [predicate] on it.
* If all the elements give null with predicate call, returns null.
*
* Note that collection is scanned only to the first non-null predicate result.
*/
fun <T,R>Collection<T>.firstNonNull(predicate: (T)->R?): R? {
for( x in this ) predicate(x)?.let { return it }
return null
}

View File

@ -1,46 +0,0 @@
package net.sergeych.utools
import kotlinx.serialization.KSerializer
import kotlinx.serialization.serializer
import net.sergeych.bintools.toDataSource
import net.sergeych.bipack.BipackDecoder
import net.sergeych.bipack.BipackEncoder
/**
* Effectively pack anyk nullable object. The result could be effectively packed
* in turn as a part of a more complex structure.
*
* To avoid packing non-null mark,
* we use a zero-size array, which, if in turn encoded, packs into a single
* zero byte. Thus, we avoid extra byte spending for unnecessary null
* check.
*/
inline fun <reified T> pack(element: T?): UByteArray = pack(serializer<T>(), element)
/**
* Unpack nullable data packed with [pack]
*/
inline fun <reified T: Any?> unpack(encoded: UByteArray): T =
unpack(serializer<T>(), encoded)
/**
* Effectively pack anyk nullable object. The result could be effectively packed
* in turn as a part of a more complex structure.
*
* To avoid packing non-null mark,
* we use a zero-size array, which, if in turn encoded, packs into a single
* zero byte. Thus, we avoid extra byte spending for unnecessary null
* check.
*/
fun <T>pack(serializer: KSerializer<T>, element: T?): UByteArray =
if (element == null) ubyteArrayOf()
else BipackEncoder.encode(serializer,element).toUByteArray()
/**
* Unpack nullable data packed with [pack]
*/
@Suppress("UNCHECKED_CAST")
fun <T: Any?> unpack(serializer: KSerializer<T>, encoded: UByteArray): T =
if (encoded.isEmpty()) null as T
else BipackDecoder.decode(encoded.toByteArray().toDataSource(),serializer)

View File

@ -1,12 +0,0 @@
@file:Suppress("unused")
package net.sergeych.utools
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
fun now(): Instant = Clock.System.now()
fun nowToSeconds(): Instant = Clock.System.now().truncateToSeconds()
fun Instant.truncateToSeconds(): Instant =
Instant.fromEpochSeconds(toEpochMilliseconds()/1000)

View File

@ -1,183 +0,0 @@
package org.komputing.khash.keccak
import com.ionspin.kotlin.bignum.integer.BigInteger
import org.komputing.khash.keccak.extensions.fillWith
import kotlin.math.min
object Keccak {
private val BIT_65 = BigInteger.ONE shl (64)
private val MAX_64_BITS = BIT_65 - BigInteger.ONE
fun digest(value: ByteArray, parameter: KeccakParameter): ByteArray {
val uState = IntArray(200)
val uMessage = convertToUInt(value)
var blockSize = 0
var inputOffset = 0
// Absorbing phase
while (inputOffset < uMessage.size) {
blockSize = min(uMessage.size - inputOffset, parameter.rateInBytes)
for (i in 0 until blockSize) {
uState[i] = uState[i] xor uMessage[i + inputOffset]
}
inputOffset += blockSize
if (blockSize == parameter.rateInBytes) {
doF(uState)
blockSize = 0
}
}
// Padding phase
uState[blockSize] = uState[blockSize] xor parameter.d
if (parameter.d and 0x80 != 0 && blockSize == parameter.rateInBytes - 1) {
doF(uState)
}
uState[parameter.rateInBytes - 1] = uState[parameter.rateInBytes - 1] xor 0x80
doF(uState)
// Squeezing phase
val byteResults = mutableListOf<Byte>()
var tOutputLen = parameter.outputLengthInBytes
while (tOutputLen > 0) {
blockSize = min(tOutputLen, parameter.rateInBytes)
for (i in 0 until blockSize) {
byteResults.add(uState[i].toByte().toInt().toByte())
}
tOutputLen -= blockSize
if (tOutputLen > 0) {
doF(uState)
}
}
return byteResults.toByteArray()
}
private fun doF(uState: IntArray) {
val lState = Array(5) { Array(5) { BigInteger.ZERO } }
for (i in 0..4) {
for (j in 0..4) {
val data = IntArray(8)
val index = 8 * (i + 5 * j)
uState.copyInto(data, 0, index, index + data.size)
lState[i][j] = convertFromLittleEndianTo64(data)
}
}
roundB(lState)
uState.fillWith(0)
for (i in 0..4) {
for (j in 0..4) {
val data = convertFrom64ToLittleEndian(lState[i][j])
data.copyInto(uState, 8 * (i + 5 * j))
}
}
}
/**
* Permutation on the given state.
*/
private fun roundB(state: Array<Array<BigInteger>>) {
var lfsrState = 1
for (round in 0..23) {
val c = arrayOfNulls<BigInteger>(5)
val d = arrayOfNulls<BigInteger>(5)
// θ step
for (i in 0..4) {
c[i] = state[i][0].xor(state[i][1]).xor(state[i][2]).xor(state[i][3]).xor(state[i][4])
}
for (i in 0..4) {
d[i] = c[(i + 4) % 5]!!.xor(c[(i + 1) % 5]!!.leftRotate64(1))
}
for (i in 0..4) {
for (j in 0..4) {
state[i][j] = state[i][j].xor(d[i]!!)
}
}
// ρ and π steps
var x = 1
var y = 0
var current = state[x][y]
for (i in 0..23) {
val tX = x
x = y
y = (2 * tX + 3 * y) % 5
val shiftValue = current
current = state[x][y]
state[x][y] = shiftValue.leftRotate64Safely((i + 1) * (i + 2) / 2)
}
// χ step
for (j in 0..4) {
val t = arrayOfNulls<BigInteger>(5)
for (i in 0..4) {
t[i] = state[i][j]
}
for (i in 0..4) {
// ~t[(i + 1) % 5]
val invertVal = t[(i + 1) % 5]!!.xor(MAX_64_BITS)
// t[i] ^ ((~t[(i + 1) % 5]) & t[(i + 2) % 5])
state[i][j] = t[i]!!.xor(invertVal.and(t[(i + 2) % 5]!!))
}
}
// ι step
for (i in 0..6) {
lfsrState = (lfsrState shl 1 xor (lfsrState shr 7) * 0x71) % 256
// pow(2, i) - 1
val bitPosition = (1 shl i) - 1
if (lfsrState and 2 != 0) {
state[0][0] = state[0][0].xor(BigInteger.ONE shl bitPosition)
}
}
}
}
/**
* Converts the given [data] array to an [IntArray] containing UInt values.
*/
private fun convertToUInt(data: ByteArray) = IntArray(data.size) {
data[it].toInt() and 0xFF
}
/**
* Converts the given [data] array containing the little endian representation of a number to a [BigInteger].
*/
private fun convertFromLittleEndianTo64(data: IntArray): BigInteger {
val value = data.map { it.toString(16) }
.map { if (it.length == 2) it else "0$it" }
.reversed()
.joinToString("")
return BigInteger.parseString(value, 16)
}
/**
* Converts the given [BigInteger] to a little endian representation as an [IntArray].
*/
private fun convertFrom64ToLittleEndian(uLong: BigInteger): IntArray {
val asHex = uLong.toString(16)
val asHexPadded = "0".repeat((8 * 2) - asHex.length) + asHex
return IntArray(8) {
((7 - it) * 2).let { pos ->
asHexPadded.substring(pos, pos + 2).toInt(16)
}
}
}
private fun BigInteger.leftRotate64Safely(rotate: Int) = leftRotate64(rotate % 64)
private fun BigInteger.leftRotate64(rotate: Int) = (this shr (64 - rotate)).add(this shl rotate).mod(BIT_65)
}

View File

@ -1,22 +0,0 @@
@file:Suppress("unused")
package org.komputing.khash.keccak
/**
* Parameters defining the FIPS 202 standard.
*/
enum class KeccakParameter(val rateInBytes: Int,val outputLengthInBytes: Int, val d: Int) {
KECCAK_224(144, 28, 0x01),
KECCAK_256(136, 32, 0x01),
KECCAK_384(104, 48, 0x01),
KECCAK_512(72, 64, 0x01),
SHA3_224(144, 28, 0x06),
SHA3_256(136, 32, 0x06),
SHA3_384(104, 48, 0x06),
SHA3_512(72, 64, 0x06),
SHAKE128(168, 32, 0x1F),
SHAKE256(136, 64, 0x1F)
}

View File

@ -1,42 +0,0 @@
package org.komputing.khash.keccak.extensions
/**
* Assigns the specified int value to each element of the specified
* range in the specified array of ints. The range to be filled
* extends from index <tt>fromIndex</tt>, inclusive, to index
* <tt>toIndex</tt>, exclusive. (If <tt>fromIndex==toIndex</tt>, the
* range to be filled is empty.)
*
* @param fromIndex the index of the first element (inclusive) to be
* filled with the specified value
* @param toIndex the index of the last element (exclusive) to be
* filled with the specified value
* @param value the value to be stored in all elements of the array
* @throws IllegalArgumentException if <tt>fromIndex &gt; toIndex</tt>
* @throws ArrayIndexOutOfBoundsException if <tt>fromIndex &lt; 0</tt> or
* <tt>toIndex &gt; a.length</tt>
*/
internal fun IntArray.fillWith(value: Int, fromIndex: Int = 0, toIndex: Int = this.size) {
if (fromIndex > toIndex) {
throw IllegalArgumentException(
"fromIndex($fromIndex) > toIndex($toIndex)"
)
}
if (fromIndex < 0) {
throw ArrayIndexOutOfBoundsException(fromIndex)
}
if (toIndex > this.size) {
throw ArrayIndexOutOfBoundsException(toIndex)
}
for (i in fromIndex until toIndex)
this[i] = value
}
/**
* Constructs a new [ArrayIndexOutOfBoundsException]
* class with an argument indicating the illegal index.
* @param index the illegal index.
*/
internal class ArrayIndexOutOfBoundsException(index: Int) : Throwable("Array index out of range: $index")

View File

@ -1,19 +0,0 @@
@file:Suppress("unused")
package org.komputing.khash.keccak.extensions
import org.komputing.khash.keccak.Keccak
import org.komputing.khash.keccak.KeccakParameter
/**
* Computes the proper Keccak digest of [this] byte array based on the given [parameter]
*/
fun ByteArray.digestKeccak(parameter: KeccakParameter): ByteArray {
return Keccak.digest(this, parameter)
}
/**
* Computes the proper Keccak digest of [this] string based on the given [parameter]
*/
fun String.digestKeccak(parameter: KeccakParameter): ByteArray {
return Keccak.digest(encodeToByteArray(), parameter)
}

View File

@ -11,7 +11,7 @@ class KeysTest {
@Test
fun testCreationAndMap() = runTest {
initCrypto()
val (stk,pbk) = SigningKey.Secret.pair()
val (stk,pbk) = SigningKey.pair()
val x = mapOf( stk to "STK!", pbk to "PBK!")
assertEquals("STK!", x[stk])
@ -22,22 +22,22 @@ class KeysTest {
val data = "8 rays dev!".encodeToUByteArray()
val data1 = "8 rays dev!".encodeToUByteArray()
val s = SignedBox.Seal.create(stk, data)
val s = stk.seal(data)
assertTrue(s.verify(data))
data1[0] = 0x01u
assertFalse(s.verify(data1))
val p2 = SigningKey.Secret.pair()
val p3 = SigningKey.Secret.pair()
val p2 = SigningKey.pair()
val p3 = SigningKey.pair()
val ms = SignedBox(data, s1) + p2.signing
val ms = SignedBox(data, s1) + p2.secretKey
// non tampered:
val ms1 = unpack<SignedBox>(pack(ms))
assertContentEquals(data, ms1.message)
assertTrue(pbk in ms1)
assertTrue(p2.aPublic in ms1)
assertTrue(p3.aPublic !in ms1)
assertTrue(p2.publicKey in ms1)
assertTrue(p3.publicKey !in ms1)
assertThrows<IllegalSignatureException> {
unpack<SignedBox>(pack(ms).also { it[3] = 1u })

View File

@ -171,8 +171,8 @@ class TransportTest {
// Log.defaultLevel = Log.Level.DEBUG
val (d1, d2) = createTestDevice()
val serverId = SigningKey.Secret.pair()
val clientId = SigningKey.Secret.pair()
val serverId = SigningKey.pair()
val clientId = SigningKey.pair()
val serverInterface = KiloInterface<String>().apply {
on(cmdPing) {
@ -193,13 +193,13 @@ class TransportTest {
registerError { IllegalStateException() }
registerError { IllegalArgumentException(it) }
}
val kiloServerConnection = KiloServerConnection(serverInterface, d1, "server session", serverId.signing)
val kiloServerConnection = KiloServerConnection(serverInterface, d1, "server session", serverId.secretKey)
launch { kiloServerConnection.run() }
var cnt = 0
val client = KiloClient {
session { "client session!" }
secretIdKey = clientId.signing
secretIdKey = clientId.secretKey
local {
on(cmdPush) {
"server push: $it"

View File

@ -1,6 +0,0 @@
package net.sergeych.tools
actual fun ProtectedOp(): ProtectedOpImplementation = object : ProtectedOpImplementation {
// JS targets are inherently single-threaded, so we do noting:
override fun <T> invoke(f: () -> T): T = f()
}

View File

@ -1,8 +0,0 @@
package net.sergeych.tools
actual fun ProtectedOp(): ProtectedOpImplementation = object : ProtectedOpImplementation {
private val lock = Object()
override fun <T> invoke(f: () -> T): T {
synchronized(lock) { return f() }
}
}

View File

@ -1,13 +0,0 @@
package net.sergeych.tools
import kotlinx.atomicfu.locks.SynchronizedObject
import kotlinx.atomicfu.locks.synchronized
actual fun ProtectedOp(): ProtectedOpImplementation = object : ProtectedOpImplementation {
private val lock = SynchronizedObject()
override fun <T> invoke(f: () -> T): T {
synchronized(lock) {
return f()
}
}
}