Working on js generator, adding initializers
This commit is contained in:
parent
93a94724f3
commit
641fbceb3f
@ -16,11 +16,16 @@ object Coordinator {
|
|||||||
val commonFileSpec = CommonLibsodiumGenerator.createCommonFile(packageName, LibSodiumDefinitions.testKotlinFile)
|
val commonFileSpec = CommonLibsodiumGenerator.createCommonFile(packageName, LibSodiumDefinitions.testKotlinFile)
|
||||||
val jvmFileSpec = JvmLibsodiumGenerator.createJvmFile(packageName, LibSodiumDefinitions.testKotlinFile)
|
val jvmFileSpec = JvmLibsodiumGenerator.createJvmFile(packageName, LibSodiumDefinitions.testKotlinFile)
|
||||||
val nativeFileSpec = NativeLibsodiumGenerator.createNativeFile(packageName, LibSodiumDefinitions.testKotlinFile)
|
val nativeFileSpec = NativeLibsodiumGenerator.createNativeFile(packageName, LibSodiumDefinitions.testKotlinFile)
|
||||||
|
val jsFileSpec = JsLibsodiumGenerator.createJsFile(packageName, LibSodiumDefinitions.testKotlinFile)
|
||||||
|
|
||||||
val commonFile = File("../multiplatform-crypto-libsodium-bindings/src/commonMain/kotlin/")
|
val commonFile = File("../multiplatform-crypto-libsodium-bindings/src/commonMain/kotlin/")
|
||||||
commonFileSpec.writeTo(commonFile)
|
commonFileSpec.writeTo(commonFile)
|
||||||
val jvmFile = File("../multiplatform-crypto-libsodium-bindings/src/jvmMain/kotlin/")
|
val jvmFile = File("../multiplatform-crypto-libsodium-bindings/src/jvmMain/kotlin/")
|
||||||
jvmFileSpec.writeTo(jvmFile)
|
jvmFileSpec.writeTo(jvmFile)
|
||||||
val nativeFile = File("../multiplatform-crypto-libsodium-bindings/src/nativeMain/kotlin/")
|
val nativeFile = File("../multiplatform-crypto-libsodium-bindings/src/nativeMain/kotlin/")
|
||||||
nativeFileSpec.writeTo(nativeFile)
|
nativeFileSpec.writeTo(nativeFile)
|
||||||
|
val jsFile = File("../multiplatform-crypto-libsodium-bindings/src/jsMain/kotlin/")
|
||||||
|
jsFileSpec.writeTo(jsFile)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,9 +13,8 @@ object JsLibsodiumGenerator {
|
|||||||
|
|
||||||
fun createJsFile(packageName: String, fileDefinition: KotlinFileDefinition): FileSpec {
|
fun createJsFile(packageName: String, fileDefinition: KotlinFileDefinition): FileSpec {
|
||||||
val fileBuilder = FileSpec.builder(packageName, fileDefinition.name)
|
val fileBuilder = FileSpec.builder(packageName, fileDefinition.name)
|
||||||
val sodiumProperty = PropertySpec.builder("sodium", ClassName.bestGuess("com.goterl.lazycode.lazysodium.SodiumJava"))
|
fileBuilder.addImport("ext.libsodium.com.ionspin.kotlin.crypto", "toUInt8Array")
|
||||||
sodiumProperty.initializer(CodeBlock.of("SodiumJava()"))
|
fileBuilder.addImport("com.ionspin.kotlin.crypto", "getSodium")
|
||||||
fileBuilder.addProperty(sodiumProperty.build())
|
|
||||||
for (commonClassDefinition in fileDefinition.commonClassList) {
|
for (commonClassDefinition in fileDefinition.commonClassList) {
|
||||||
//Create type-aliases
|
//Create type-aliases
|
||||||
commonClassDefinition.innerClasses.forEach {
|
commonClassDefinition.innerClasses.forEach {
|
||||||
@ -38,7 +37,7 @@ object JsLibsodiumGenerator {
|
|||||||
innerClassDefinition: InnerClassDefinition,
|
innerClassDefinition: InnerClassDefinition,
|
||||||
multiplatformModifier: MultiplatformModifier
|
multiplatformModifier: MultiplatformModifier
|
||||||
): TypeAliasSpec {
|
): TypeAliasSpec {
|
||||||
val innerClassBuilder = TypeAliasSpec.builder(innerClassDefinition.name, ClassName.bestGuess(innerClassDefinition.javaName))
|
val innerClassBuilder = TypeAliasSpec.builder(innerClassDefinition.name, Any::class.asTypeName())
|
||||||
innerClassBuilder.modifiers += multiplatformModifier.modifierList
|
innerClassBuilder.modifiers += multiplatformModifier.modifierList
|
||||||
|
|
||||||
return innerClassBuilder.build()
|
return innerClassBuilder.build()
|
||||||
@ -65,7 +64,7 @@ object JsLibsodiumGenerator {
|
|||||||
if (methodDefinition.returnType == TypeDefinition.ARRAY_OF_UBYTES) {
|
if (methodDefinition.returnType == TypeDefinition.ARRAY_OF_UBYTES) {
|
||||||
methodBuilder.addStatement("println(\"Debug\")")
|
methodBuilder.addStatement("println(\"Debug\")")
|
||||||
val constructJvmCall = StringBuilder()
|
val constructJvmCall = StringBuilder()
|
||||||
constructJvmCall.append("return sodium.${methodDefinition.javaName}")
|
constructJvmCall.append("return getSodium().${methodDefinition.javaName}")
|
||||||
constructJvmCall.append(paramsToString(methodDefinition))
|
constructJvmCall.append(paramsToString(methodDefinition))
|
||||||
|
|
||||||
methodBuilder.addStatement(constructJvmCall.toString())
|
methodBuilder.addStatement(constructJvmCall.toString())
|
||||||
@ -74,7 +73,7 @@ object JsLibsodiumGenerator {
|
|||||||
if (methodDefinition.returnType == TypeDefinition.INT) {
|
if (methodDefinition.returnType == TypeDefinition.INT) {
|
||||||
methodBuilder.addStatement("println(\"Debug\")")
|
methodBuilder.addStatement("println(\"Debug\")")
|
||||||
val constructJvmCall = StringBuilder()
|
val constructJvmCall = StringBuilder()
|
||||||
constructJvmCall.append("return sodium.${methodDefinition.javaName}")
|
constructJvmCall.append("return getSodium().${methodDefinition.javaName}")
|
||||||
constructJvmCall.append(paramsToString(methodDefinition))
|
constructJvmCall.append(paramsToString(methodDefinition))
|
||||||
|
|
||||||
methodBuilder.addStatement(constructJvmCall.toString())
|
methodBuilder.addStatement(constructJvmCall.toString())
|
||||||
@ -83,7 +82,7 @@ object JsLibsodiumGenerator {
|
|||||||
if (methodDefinition.returnType == TypeDefinition.UNIT) {
|
if (methodDefinition.returnType == TypeDefinition.UNIT) {
|
||||||
methodBuilder.addStatement("println(\"Debug\")")
|
methodBuilder.addStatement("println(\"Debug\")")
|
||||||
val constructJvmCall = StringBuilder()
|
val constructJvmCall = StringBuilder()
|
||||||
constructJvmCall.append("sodium.${methodDefinition.javaName}")
|
constructJvmCall.append("getSodium().${methodDefinition.javaName}")
|
||||||
constructJvmCall.append(paramsToString(methodDefinition))
|
constructJvmCall.append(paramsToString(methodDefinition))
|
||||||
|
|
||||||
methodBuilder.addStatement(constructJvmCall.toString())
|
methodBuilder.addStatement(constructJvmCall.toString())
|
||||||
@ -92,7 +91,7 @@ object JsLibsodiumGenerator {
|
|||||||
if (methodDefinition.returnType is CustomTypeDefinition) {
|
if (methodDefinition.returnType is CustomTypeDefinition) {
|
||||||
methodBuilder.addStatement("println(\"Debug\")")
|
methodBuilder.addStatement("println(\"Debug\")")
|
||||||
val constructJvmCall = StringBuilder()
|
val constructJvmCall = StringBuilder()
|
||||||
constructJvmCall.append("return sodium.${methodDefinition.javaName}")
|
constructJvmCall.append("return getSodium().${methodDefinition.javaName}")
|
||||||
constructJvmCall.append(paramsToString(methodDefinition))
|
constructJvmCall.append(paramsToString(methodDefinition))
|
||||||
|
|
||||||
methodBuilder.addStatement(constructJvmCall.toString())
|
methodBuilder.addStatement(constructJvmCall.toString())
|
||||||
@ -117,13 +116,13 @@ object JsLibsodiumGenerator {
|
|||||||
if (paramDefinition.parameterType is TypeDefinition) {
|
if (paramDefinition.parameterType is TypeDefinition) {
|
||||||
when(paramDefinition.parameterType) {
|
when(paramDefinition.parameterType) {
|
||||||
TypeDefinition.ARRAY_OF_UBYTES -> {
|
TypeDefinition.ARRAY_OF_UBYTES -> {
|
||||||
paramsBuilder.append(paramDefinition.parameterName + ".asByteArray(), " + paramDefinition.parameterName + ".size" + separator)
|
paramsBuilder.append(paramDefinition.parameterName + ".toUInt8Array(), " + paramDefinition.parameterName + ".size" + separator)
|
||||||
}
|
}
|
||||||
TypeDefinition.ARRAY_OF_UBYTES_LONG_SIZE -> {
|
TypeDefinition.ARRAY_OF_UBYTES_LONG_SIZE -> {
|
||||||
paramsBuilder.append(paramDefinition.parameterName + ".asByteArray(), " + paramDefinition.parameterName + ".size.toLong()" + separator)
|
paramsBuilder.append(paramDefinition.parameterName + ".toUInt8Array(), " + paramDefinition.parameterName + ".size.toLong()" + separator)
|
||||||
}
|
}
|
||||||
TypeDefinition.ARRAY_OF_UBYTES_NO_SIZE -> {
|
TypeDefinition.ARRAY_OF_UBYTES_NO_SIZE -> {
|
||||||
paramsBuilder.append(paramDefinition.parameterName + ".asByteArray()" + separator)
|
paramsBuilder.append(paramDefinition.parameterName + ".toUInt8Array()" + separator)
|
||||||
}
|
}
|
||||||
TypeDefinition.LONG -> {
|
TypeDefinition.LONG -> {
|
||||||
paramsBuilder.append(paramDefinition.parameterName + separator)
|
paramsBuilder.append(paramDefinition.parameterName + separator)
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
package com.ionspin.kotlin.crypto
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Ugljesa Jovanovic (jovanovic.ugljesa@gmail.com) on 02/Aug/2020
|
||||||
|
*/
|
||||||
|
expect object Initializer {
|
||||||
|
fun isInitialized() : Boolean
|
||||||
|
|
||||||
|
suspend fun initialize()
|
||||||
|
|
||||||
|
fun initializeWithCallback(done: () -> (Unit))
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package com.ionspin.kotlin.crypto
|
||||||
|
|
||||||
|
import ext.libsodium.com.ionspin.kotlin.crypto.JsSodiumInterface
|
||||||
|
import ext.libsodium.com.ionspin.kotlin.crypto.JsSodiumLoader
|
||||||
|
/* 1.4-M1 has some weirdness with static/objects, or I'm misusing something, not sure */
|
||||||
|
lateinit var sodiumPointer : JsSodiumInterface
|
||||||
|
var sodiumLoaded: Boolean = false
|
||||||
|
|
||||||
|
fun getSodium() : JsSodiumInterface = sodiumPointer
|
||||||
|
|
||||||
|
//fun getSodiumAdvanced() : JsSodiumAdvancedInterface = js("sodiumPointer.libsodium")
|
||||||
|
|
||||||
|
fun setSodiumPointer(jsSodiumInterface: JsSodiumInterface) {
|
||||||
|
js("sodiumPointer = jsSodiumInterface")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSodiumLoaded() : Boolean = sodiumLoaded
|
||||||
|
|
||||||
|
fun setSodiumLoaded(loaded: Boolean) {
|
||||||
|
js("sodiumLoaded = loaded")
|
||||||
|
}
|
||||||
|
|
||||||
|
actual object Initializer {
|
||||||
|
private var isPlatformInitialized = false
|
||||||
|
|
||||||
|
actual suspend fun initialize() {
|
||||||
|
JsSodiumLoader.load()
|
||||||
|
isPlatformInitialized = true
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun initializeWithCallback(done: () -> Unit) {
|
||||||
|
JsSodiumLoader.loadWithCallback {
|
||||||
|
isPlatformInitialized = true
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun isInitialized(): Boolean {
|
||||||
|
return isPlatformInitialized
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
package ext.libsodium.com.ionspin.kotlin.crypto
|
||||||
|
|
||||||
|
|
||||||
|
import org.khronos.webgl.Uint8Array
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Ugljesa Jovanovic
|
||||||
|
* ugljesa.jovanovic@ionspin.com
|
||||||
|
* on 27-May-2020
|
||||||
|
*/
|
||||||
|
interface JsSodiumInterface {
|
||||||
|
|
||||||
|
fun randombytes_buf(numberOfBytes: Int): Uint8Array
|
||||||
|
|
||||||
|
fun crypto_generichash(hashLength: Int, inputMessage: Uint8Array, key: Uint8Array,): Uint8Array
|
||||||
|
|
||||||
|
fun crypto_hash_sha256(message: Uint8Array): Uint8Array
|
||||||
|
|
||||||
|
fun crypto_hash_sha512(message: Uint8Array): Uint8Array
|
||||||
|
|
||||||
|
//Updateable
|
||||||
|
|
||||||
|
fun crypto_generichash_init(key : Uint8Array, hashLength: Int) : dynamic
|
||||||
|
|
||||||
|
fun crypto_generichash_update(state: dynamic, inputMessage: Uint8Array)
|
||||||
|
|
||||||
|
fun crypto_generichash_final(state: dynamic, hashLength: Int) : Uint8Array
|
||||||
|
|
||||||
|
|
||||||
|
fun crypto_hash_sha256_init() : dynamic
|
||||||
|
|
||||||
|
fun crypto_hash_sha256_update(state: dynamic, message: Uint8Array)
|
||||||
|
|
||||||
|
fun crypto_hash_sha256_final(state: dynamic): Uint8Array
|
||||||
|
|
||||||
|
fun crypto_hash_sha512_init() : dynamic
|
||||||
|
|
||||||
|
fun crypto_hash_sha512_update(state: dynamic, message: Uint8Array)
|
||||||
|
|
||||||
|
fun crypto_hash_sha512_final(state: dynamic): Uint8Array
|
||||||
|
|
||||||
|
//XChaCha20Poly1305
|
||||||
|
fun crypto_aead_xchacha20poly1305_ietf_encrypt(message: Uint8Array, additionalData: Uint8Array, secretNonce: Uint8Array, nonce: Uint8Array, key: Uint8Array) : Uint8Array
|
||||||
|
fun crypto_aead_xchacha20poly1305_ietf_decrypt(secretNonce: Uint8Array, ciphertext: Uint8Array, additionalData: Uint8Array, nonce: Uint8Array, key: Uint8Array) : Uint8Array
|
||||||
|
|
||||||
|
//XChaCha20Poly1305
|
||||||
|
//encrypt
|
||||||
|
fun crypto_secretstream_xchacha20poly1305_init_push(header: Uint8Array) : dynamic
|
||||||
|
fun crypto_secretstream_xchacha20poly1305_push(state: dynamic, message: Uint8Array, additionalData: Uint8Array, tag: UByte) : Uint8Array
|
||||||
|
|
||||||
|
//decrypt
|
||||||
|
fun crypto_secretstream_xchacha20poly1305_init_pull(header: Uint8Array, key: Uint8Array) : dynamic
|
||||||
|
fun crypto_secretstream_xchacha20poly1305_pull(state: dynamic, ciphertext: Uint8Array, additionalData: Uint8Array) : dynamic
|
||||||
|
|
||||||
|
//util
|
||||||
|
fun memzero(array: Uint8Array)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
package ext.libsodium.com.ionspin.kotlin.crypto
|
||||||
|
|
||||||
|
import com.ionspin.kotlin.crypto.getSodiumLoaded
|
||||||
|
import com.ionspin.kotlin.crypto.setSodiumPointer
|
||||||
|
import com.ionspin.kotlin.crypto.sodiumLoaded
|
||||||
|
import ext.libsodium.*
|
||||||
|
import kotlin.coroutines.Continuation
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Ugljesa Jovanovic
|
||||||
|
* ugljesa.jovanovic@ionspin.com
|
||||||
|
* on 27-May-2020
|
||||||
|
*/
|
||||||
|
object JsSodiumLoader {
|
||||||
|
|
||||||
|
class _EmitJsSodiumFunction {
|
||||||
|
init {
|
||||||
|
println(::crypto_generichash)
|
||||||
|
println(::crypto_hash_sha256)
|
||||||
|
println(::crypto_hash_sha512)
|
||||||
|
println(::crypto_hash_sha256_init)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun storeSodium(promisedSodium: dynamic, continuation: Continuation<Unit>) {
|
||||||
|
setSodiumPointer(promisedSodium)
|
||||||
|
sodiumLoaded = true
|
||||||
|
continuation.resumeWith(Result.success(Unit))
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun load() = suspendCoroutine<Unit> { continuation ->
|
||||||
|
console.log(getSodiumLoaded())
|
||||||
|
if (!getSodiumLoaded()) {
|
||||||
|
val libsodiumModule = js("\$module\$libsodium_wrappers_sumo")
|
||||||
|
_libsodiumPromise.then<dynamic> { storeSodium(libsodiumModule, continuation) }
|
||||||
|
} else {
|
||||||
|
continuation.resumeWith(Result.success(Unit))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadWithCallback(doneCallback: () -> (Unit)) {
|
||||||
|
console.log(getSodiumLoaded())
|
||||||
|
if (!getSodiumLoaded()) {
|
||||||
|
val libsodiumModule = js("\$module\$libsodium_wrappers_sumo")
|
||||||
|
_libsodiumPromise.then<dynamic> {
|
||||||
|
setSodiumPointer(libsodiumModule)
|
||||||
|
sodiumLoaded = true
|
||||||
|
doneCallback.invoke()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
doneCallback.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
package debug.test
|
||||||
|
|
||||||
|
import com.ionspin.kotlin.crypto.getSodium
|
||||||
|
import ext.libsodium.com.ionspin.kotlin.crypto.toUInt8Array
|
||||||
|
import kotlin.Any
|
||||||
|
import kotlin.Int
|
||||||
|
import kotlin.UByteArray
|
||||||
|
|
||||||
|
actual typealias Sha256State = Any
|
||||||
|
|
||||||
|
actual typealias Sha512State = Any
|
||||||
|
|
||||||
|
actual typealias GenericHashState = Any
|
||||||
|
|
||||||
|
actual class Crypto {
|
||||||
|
actual fun crypto_hash_sha256_init(state: Sha256State): Int {
|
||||||
|
println("Debug")
|
||||||
|
return getSodium().crypto_hash_sha256_init(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun crypto_hash_sha256_update(state: Sha256State, input: UByteArray) {
|
||||||
|
println("Debug")
|
||||||
|
getSodium().crypto_hash_sha256_update(state, input.toUInt8Array(), input.size.toLong())
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun crypto_hash_sha256_final(state: Sha256State, out: UByteArray) {
|
||||||
|
println("Debug")
|
||||||
|
getSodium().crypto_hash_sha256_final(state, out.toUInt8Array())
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun crypto_hash_sha512_init(state: Sha512State): Int {
|
||||||
|
println("Debug")
|
||||||
|
return getSodium().crypto_hash_sha512_init(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun crypto_hash_sha512_update(state: Sha512State, input: UByteArray) {
|
||||||
|
println("Debug")
|
||||||
|
getSodium().crypto_hash_sha512_update(state, input.toUInt8Array(), input.size.toLong())
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun crypto_hash_sha512_final(state: Sha512State, out: UByteArray) {
|
||||||
|
println("Debug")
|
||||||
|
getSodium().crypto_hash_sha512_final(state, out.toUInt8Array())
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun crypto_generichash_init(
|
||||||
|
state: GenericHashState,
|
||||||
|
key: UByteArray,
|
||||||
|
outlen: Int
|
||||||
|
): Int {
|
||||||
|
println("Debug")
|
||||||
|
return getSodium().crypto_generichash_init(state, key.toUInt8Array(), key.size, outlen)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package com.ionspin.kotlin.crypto
|
||||||
|
|
||||||
|
import com.goterl.lazycode.lazysodium.SodiumJava
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Ugljesa Jovanovic (jovanovic.ugljesa@gmail.com) on 02/Aug/2020
|
||||||
|
*/
|
||||||
|
actual object Initializer {
|
||||||
|
private var isPlatformInitialized = false
|
||||||
|
|
||||||
|
lateinit var sodium : SodiumJava
|
||||||
|
actual suspend fun initialize() {
|
||||||
|
sodium = SodiumJava()
|
||||||
|
isPlatformInitialized = true
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun initializeWithCallback(done: () -> Unit) {
|
||||||
|
sodium = SodiumJava()
|
||||||
|
isPlatformInitialized = true
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun isInitialized(): Boolean {
|
||||||
|
return isPlatformInitialized
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
@file:Suppress("VARIABLE_IN_SINGLETON_WITHOUT_THREAD_LOCAL")
|
||||||
|
|
||||||
|
package com.ionspin.kotlin.crypto
|
||||||
|
|
||||||
|
import libsodium.sodium_init
|
||||||
|
import kotlin.native.concurrent.AtomicInt
|
||||||
|
|
||||||
|
actual object Initializer {
|
||||||
|
|
||||||
|
private var isPlatformInitialized : AtomicInt = AtomicInt(0)
|
||||||
|
|
||||||
|
actual suspend fun initialize() {
|
||||||
|
if (isPlatformInitialized.compareAndSet(0, 1)) {
|
||||||
|
sodium_init()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun initializeWithCallback(done: () -> Unit) {
|
||||||
|
if (isPlatformInitialized.compareAndSet(0, 1)) {
|
||||||
|
sodium_init()
|
||||||
|
}
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun isInitialized(): Boolean {
|
||||||
|
return isPlatformInitialized.value != 0
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user