feat: add linuxX64 KMP target and restructure native source sets
Restructure quartz module's Kotlin/Native source set hierarchy for
proper multiplatform coverage. Adds linuxX64 target with complete
actual implementations for all expect declarations.
Source set hierarchy:
commonMain
├── jvmAndroid → jvmMain, androidMain
└── nativeMain (shared pure Kotlin)
├── appleMain (Apple APIs) → iosMain
└── linuxMain (POSIX/OpenSSL) → linuxX64Main
nativeMain: Address, OptimizedJsonMapper, EventHasherSerializer,
ChessEngine, Secp256k1Instance, BitSet, StringExt, io/ utilities
appleMain: Log (NSLog), SecureRandom (SecRandomCopyBytes),
UriParser (NSURLComponents), BigDecimal (NSDecimalNumber),
GZip (zlib), Sha256 (CC_SHA256), crypto (Apple provider),
LargeCache (CacheMap), UrlEncoder, UnicodeNormalizer
linuxMain: Log (println), SecureRandom (/dev/urandom),
UriParser (pure Kotlin), BigDecimal (pure Kotlin),
GZip (zlib cinterop), Sha256 (OpenSSL), crypto (OpenSSL),
LargeCache (AtomicReference<LinkedHashMap>), UrlEncoder
macOS targets deferred pending negentropy-kmp macOS artifacts.
macosMain/Platform.kt retained as scaffold for future activation.
https://claude.ai/code/session_01M9CAuPUV3TfPB2xShnsBhw
This commit is contained in:
+57
-11
@@ -75,6 +75,8 @@ kotlin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
linuxX64()
|
||||||
|
|
||||||
// This makes sure that the resource file directory is visible for iOS tests.
|
// This makes sure that the resource file directory is visible for iOS tests.
|
||||||
val rootDir = "${rootProject.rootDir.path}/quartz/src/commonTest/resources"
|
val rootDir = "${rootProject.rootDir.path}/quartz/src/commonTest/resources"
|
||||||
|
|
||||||
@@ -222,15 +224,37 @@ kotlin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
iosMain {
|
// Must be defined before appleMain, linuxMain, etc.
|
||||||
dependsOn(commonMain.get())
|
val nativeMain =
|
||||||
dependencies {
|
create("nativeMain") {
|
||||||
implementation(libs.charlietap.cachemap)
|
dependsOn(commonMain.get())
|
||||||
implementation(libs.net.thauvin.erik.urlencoder.lib)
|
|
||||||
implementation(libs.dev.whyoleg.cryptography.provider.apple.optimal)
|
|
||||||
implementation("io.github.andreypfau:kotlinx-crypto-hmac:0.0.4")
|
|
||||||
implementation("io.github.andreypfau:kotlinx-crypto-sha2:0.0.4")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val nativeTest =
|
||||||
|
create("nativeTest") {
|
||||||
|
dependsOn(commonTest.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be defined before iosMain and macosMain
|
||||||
|
val appleMain =
|
||||||
|
create("appleMain") {
|
||||||
|
dependsOn(nativeMain)
|
||||||
|
dependencies {
|
||||||
|
implementation(libs.charlietap.cachemap)
|
||||||
|
implementation(libs.net.thauvin.erik.urlencoder.lib)
|
||||||
|
implementation(libs.dev.whyoleg.cryptography.provider.apple.optimal)
|
||||||
|
implementation("io.github.andreypfau:kotlinx-crypto-hmac:0.0.4")
|
||||||
|
implementation("io.github.andreypfau:kotlinx-crypto-sha2:0.0.4")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val appleTest =
|
||||||
|
create("appleTest") {
|
||||||
|
dependsOn(nativeTest)
|
||||||
|
}
|
||||||
|
|
||||||
|
iosMain {
|
||||||
|
dependsOn(appleMain)
|
||||||
}
|
}
|
||||||
|
|
||||||
val iosArm64Main by getting {
|
val iosArm64Main by getting {
|
||||||
@@ -242,9 +266,7 @@ kotlin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
iosTest {
|
iosTest {
|
||||||
dependsOn(commonTest.get())
|
dependsOn(appleTest)
|
||||||
dependencies {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val iosArm64Test by getting {
|
val iosArm64Test by getting {
|
||||||
@@ -254,6 +276,30 @@ kotlin {
|
|||||||
val iosSimulatorArm64Test by getting {
|
val iosSimulatorArm64Test by getting {
|
||||||
dependsOn(iosTest.get())
|
dependsOn(iosTest.get())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val linuxMain =
|
||||||
|
create("linuxMain") {
|
||||||
|
dependsOn(nativeMain)
|
||||||
|
dependencies {
|
||||||
|
implementation(libs.net.thauvin.erik.urlencoder.lib)
|
||||||
|
implementation(libs.dev.whyoleg.cryptography.provider.apple.optimal)
|
||||||
|
implementation("io.github.andreypfau:kotlinx-crypto-hmac:0.0.4")
|
||||||
|
implementation("io.github.andreypfau:kotlinx-crypto-sha2:0.0.4")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val linuxTest =
|
||||||
|
create("linuxTest") {
|
||||||
|
dependsOn(nativeTest)
|
||||||
|
}
|
||||||
|
|
||||||
|
val linuxX64Main by getting {
|
||||||
|
dependsOn(linuxMain)
|
||||||
|
}
|
||||||
|
|
||||||
|
val linuxX64Test by getting {
|
||||||
|
dependsOn(linuxTest)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.nip96FileStorage.info
|
||||||
|
|
||||||
|
actual fun makeAbsoluteIfRelativeUrl(
|
||||||
|
baseUrl: String,
|
||||||
|
potentiallyRelativeUrl: String,
|
||||||
|
): String =
|
||||||
|
try {
|
||||||
|
if (potentiallyRelativeUrl.contains("://")) {
|
||||||
|
potentiallyRelativeUrl
|
||||||
|
} else {
|
||||||
|
val base = if (baseUrl.endsWith("/")) baseUrl else "$baseUrl/"
|
||||||
|
if (potentiallyRelativeUrl.startsWith("/")) {
|
||||||
|
// Absolute path - combine with scheme + host from base
|
||||||
|
val schemeEnd = base.indexOf("://")
|
||||||
|
if (schemeEnd >= 0) {
|
||||||
|
val hostEnd = base.indexOf('/', schemeEnd + 3)
|
||||||
|
if (hostEnd >= 0) {
|
||||||
|
base.substring(0, hostEnd) + potentiallyRelativeUrl
|
||||||
|
} else {
|
||||||
|
base + potentiallyRelativeUrl.removePrefix("/")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
potentiallyRelativeUrl
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Relative path
|
||||||
|
base + potentiallyRelativeUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
potentiallyRelativeUrl
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.utils
|
||||||
|
|
||||||
|
actual class BigDecimal internal constructor(
|
||||||
|
private val value: String,
|
||||||
|
) : Number() {
|
||||||
|
actual constructor(strVal: String) : this(strVal.trim())
|
||||||
|
|
||||||
|
actual constructor(doubleVal: Double) : this(doubleVal.toBigDecimalString())
|
||||||
|
|
||||||
|
actual constructor(intVal: Int) : this(intVal.toString())
|
||||||
|
|
||||||
|
actual constructor(longVal: Long) : this(longVal.toString())
|
||||||
|
|
||||||
|
actual fun add(augend: BigDecimal): BigDecimal = BigDecimal((toDouble() + augend.toDouble()).toBigDecimalString())
|
||||||
|
|
||||||
|
actual fun subtract(subtrahend: BigDecimal): BigDecimal = BigDecimal((toDouble() - subtrahend.toDouble()).toBigDecimalString())
|
||||||
|
|
||||||
|
actual fun multiply(multiplicand: BigDecimal): BigDecimal = BigDecimal((toDouble() * multiplicand.toDouble()).toBigDecimalString())
|
||||||
|
|
||||||
|
actual fun divide(divisor: BigDecimal): BigDecimal = BigDecimal((toDouble() / divisor.toDouble()).toBigDecimalString())
|
||||||
|
|
||||||
|
actual fun negate(): BigDecimal = BigDecimal((-toDouble()).toBigDecimalString())
|
||||||
|
|
||||||
|
actual fun signum(): Int {
|
||||||
|
val d = toDouble()
|
||||||
|
return when {
|
||||||
|
d < 0.0 -> -1
|
||||||
|
d > 0.0 -> 1
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toInt(): Int = toDouble().toInt()
|
||||||
|
|
||||||
|
override fun toLong(): Long = toDouble().toLong()
|
||||||
|
|
||||||
|
override fun toShort(): Short = toDouble().toInt().toShort()
|
||||||
|
|
||||||
|
override fun toByte(): Byte = toDouble().toInt().toByte()
|
||||||
|
|
||||||
|
override fun toDouble(): Double = value.toDouble()
|
||||||
|
|
||||||
|
override fun toFloat(): Float = value.toFloat()
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean = (this === other) || value == (other as? BigDecimal)?.value
|
||||||
|
|
||||||
|
override fun hashCode(): Int = value.hashCode()
|
||||||
|
|
||||||
|
override fun toString(): String = value
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Double.toBigDecimalString(): String {
|
||||||
|
if (this == this.toLong().toDouble()) {
|
||||||
|
return this.toLong().toString()
|
||||||
|
}
|
||||||
|
return this.toString()
|
||||||
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.utils
|
||||||
|
|
||||||
|
import kotlinx.cinterop.ExperimentalForeignApi
|
||||||
|
import kotlinx.cinterop.addressOf
|
||||||
|
import kotlinx.cinterop.alloc
|
||||||
|
import kotlinx.cinterop.memScoped
|
||||||
|
import kotlinx.cinterop.ptr
|
||||||
|
import kotlinx.cinterop.reinterpret
|
||||||
|
import kotlinx.cinterop.usePinned
|
||||||
|
import platform.zlib.Z_DEFAULT_COMPRESSION
|
||||||
|
import platform.zlib.Z_DEFAULT_STRATEGY
|
||||||
|
import platform.zlib.Z_DEFLATED
|
||||||
|
import platform.zlib.Z_FINISH
|
||||||
|
import platform.zlib.Z_NO_FLUSH
|
||||||
|
import platform.zlib.Z_OK
|
||||||
|
import platform.zlib.Z_STREAM_END
|
||||||
|
import platform.zlib.deflate
|
||||||
|
import platform.zlib.deflateBound
|
||||||
|
import platform.zlib.deflateEnd
|
||||||
|
import platform.zlib.deflateInit2
|
||||||
|
import platform.zlib.inflate
|
||||||
|
import platform.zlib.inflateEnd
|
||||||
|
import platform.zlib.inflateInit2
|
||||||
|
import platform.zlib.z_stream
|
||||||
|
|
||||||
|
@OptIn(ExperimentalForeignApi::class)
|
||||||
|
actual object GZip {
|
||||||
|
actual fun compress(content: String): ByteArray {
|
||||||
|
val input = content.encodeToByteArray()
|
||||||
|
|
||||||
|
return memScoped {
|
||||||
|
val stream = alloc<z_stream>()
|
||||||
|
|
||||||
|
deflateInit2(
|
||||||
|
stream.ptr,
|
||||||
|
Z_DEFAULT_COMPRESSION,
|
||||||
|
Z_DEFLATED,
|
||||||
|
31,
|
||||||
|
8,
|
||||||
|
Z_DEFAULT_STRATEGY,
|
||||||
|
).let { check(it == Z_OK) { "deflateInit2 failed: $it" } }
|
||||||
|
|
||||||
|
val maxSize = deflateBound(stream.ptr, input.size.toULong()).toInt()
|
||||||
|
val output = ByteArray(maxSize)
|
||||||
|
|
||||||
|
val written =
|
||||||
|
input.usePinned { pinIn ->
|
||||||
|
output.usePinned { pinOut ->
|
||||||
|
if (input.isNotEmpty()) {
|
||||||
|
stream.next_in = pinIn.addressOf(0).reinterpret()
|
||||||
|
}
|
||||||
|
stream.avail_in = input.size.toUInt()
|
||||||
|
|
||||||
|
if (output.isNotEmpty()) {
|
||||||
|
stream.next_out = pinOut.addressOf(0).reinterpret()
|
||||||
|
}
|
||||||
|
stream.avail_out = maxSize.toUInt()
|
||||||
|
|
||||||
|
deflate(stream.ptr, Z_FINISH)
|
||||||
|
.let { check(it == Z_STREAM_END) { "deflate failed: $it" } }
|
||||||
|
|
||||||
|
maxSize - stream.avail_out.toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deflateEnd(stream.ptr)
|
||||||
|
output.copyOf(written)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun decompress(content: ByteArray): String {
|
||||||
|
if (content.isEmpty()) return ""
|
||||||
|
|
||||||
|
val chunks = ArrayList<ByteArray>()
|
||||||
|
val chunkSize = maxOf(content.size * 4, 4096)
|
||||||
|
|
||||||
|
memScoped {
|
||||||
|
val stream = alloc<z_stream>()
|
||||||
|
|
||||||
|
inflateInit2(stream.ptr, 47)
|
||||||
|
.let { check(it == Z_OK) { "inflateInit2 failed: $it" } }
|
||||||
|
|
||||||
|
content.usePinned { pinIn ->
|
||||||
|
if (content.isNotEmpty()) {
|
||||||
|
stream.next_in = pinIn.addressOf(0).reinterpret()
|
||||||
|
}
|
||||||
|
stream.avail_in = content.size.toUInt()
|
||||||
|
|
||||||
|
var status: Int = Z_OK
|
||||||
|
do {
|
||||||
|
val chunk = ByteArray(chunkSize)
|
||||||
|
chunk.usePinned { pinOut ->
|
||||||
|
stream.next_out = pinOut.addressOf(0).reinterpret()
|
||||||
|
stream.avail_out = chunkSize.toUInt()
|
||||||
|
status = inflate(stream.ptr, Z_NO_FLUSH)
|
||||||
|
val produced = chunkSize - stream.avail_out.toInt()
|
||||||
|
if (produced > 0) chunks.add(chunk.copyOf(produced))
|
||||||
|
}
|
||||||
|
} while (status == Z_OK)
|
||||||
|
|
||||||
|
check(status == Z_STREAM_END) { "inflate failed: $status" }
|
||||||
|
}
|
||||||
|
|
||||||
|
inflateEnd(stream.ptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
val totalSize = chunks.sumOf { it.size }
|
||||||
|
val result = ByteArray(totalSize)
|
||||||
|
var pos = 0
|
||||||
|
chunks.forEach { chunk ->
|
||||||
|
chunk.copyInto(result, pos)
|
||||||
|
pos += chunk.size
|
||||||
|
}
|
||||||
|
return result.decodeToString()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.utils
|
||||||
|
|
||||||
|
actual object Log {
|
||||||
|
actual fun w(
|
||||||
|
tag: String,
|
||||||
|
message: String,
|
||||||
|
throwable: Throwable?,
|
||||||
|
) {
|
||||||
|
if (throwable != null) {
|
||||||
|
println("WARN: [$tag] $message. Throwable: $throwable CAUSE ${throwable.cause}")
|
||||||
|
} else {
|
||||||
|
println("WARN: [$tag] $message")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun e(
|
||||||
|
tag: String,
|
||||||
|
message: String,
|
||||||
|
throwable: Throwable?,
|
||||||
|
) {
|
||||||
|
if (throwable != null) {
|
||||||
|
println("ERROR: [$tag] $message. Throwable: $throwable CAUSE ${throwable.cause}")
|
||||||
|
} else {
|
||||||
|
println("ERROR: [$tag] $message")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun d(
|
||||||
|
tag: String,
|
||||||
|
message: String,
|
||||||
|
) {
|
||||||
|
println("DEBUG: [$tag] $message")
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun i(
|
||||||
|
tag: String,
|
||||||
|
message: String,
|
||||||
|
) {
|
||||||
|
println("INFO: [$tag] $message")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.utils
|
||||||
|
|
||||||
|
import kotlinx.cinterop.ExperimentalForeignApi
|
||||||
|
import kotlinx.cinterop.alloc
|
||||||
|
import kotlinx.cinterop.memScoped
|
||||||
|
import kotlinx.cinterop.ptr
|
||||||
|
import platform.posix.CLOCK_REALTIME
|
||||||
|
import platform.posix.clock_gettime
|
||||||
|
import platform.posix.timespec
|
||||||
|
|
||||||
|
actual fun platform() = "Linux"
|
||||||
|
|
||||||
|
@OptIn(ExperimentalForeignApi::class)
|
||||||
|
actual fun currentTimeSeconds(): Long {
|
||||||
|
memScoped {
|
||||||
|
val ts = alloc<timespec>()
|
||||||
|
clock_gettime(CLOCK_REALTIME, ts.ptr)
|
||||||
|
return ts.tv_sec
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.utils
|
||||||
|
|
||||||
|
import kotlinx.cinterop.ExperimentalForeignApi
|
||||||
|
import kotlinx.cinterop.addressOf
|
||||||
|
import kotlinx.cinterop.convert
|
||||||
|
import kotlinx.cinterop.usePinned
|
||||||
|
import platform.posix.O_RDONLY
|
||||||
|
import platform.posix.close
|
||||||
|
import platform.posix.open
|
||||||
|
import platform.posix.read
|
||||||
|
|
||||||
|
actual class SecureRandom {
|
||||||
|
actual fun nextInt(): Int {
|
||||||
|
val bytes = ByteArray(4)
|
||||||
|
nextBytes(bytes)
|
||||||
|
return ((bytes[0].toInt() and 0xFF) shl 24) or
|
||||||
|
((bytes[1].toInt() and 0xFF) shl 16) or
|
||||||
|
((bytes[2].toInt() and 0xFF) shl 8) or
|
||||||
|
(bytes[3].toInt() and 0xFF)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun nextInt(bound: Int): Int {
|
||||||
|
require(bound > 0) { throw IllegalArgumentException("Bad Bound $bound") }
|
||||||
|
|
||||||
|
var intValue = nextPositiveInt()
|
||||||
|
|
||||||
|
val m = bound - 1
|
||||||
|
if ((bound and m) == 0) {
|
||||||
|
intValue = ((bound * intValue.toLong()) shr 31).toInt()
|
||||||
|
} else {
|
||||||
|
var u: Int = intValue
|
||||||
|
intValue = u % bound
|
||||||
|
while (u - intValue + m < 0) {
|
||||||
|
u = nextPositiveInt()
|
||||||
|
intValue = u % bound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return intValue
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun nextLong(): Long {
|
||||||
|
val bytes = ByteArray(8)
|
||||||
|
nextBytes(bytes)
|
||||||
|
return ((bytes[0].toLong() and 0xFFL) shl 56) or
|
||||||
|
((bytes[1].toLong() and 0xFFL) shl 48) or
|
||||||
|
((bytes[2].toLong() and 0xFFL) shl 40) or
|
||||||
|
((bytes[3].toLong() and 0xFFL) shl 32) or
|
||||||
|
((bytes[4].toLong() and 0xFFL) shl 24) or
|
||||||
|
((bytes[5].toLong() and 0xFFL) shl 16) or
|
||||||
|
((bytes[6].toLong() and 0xFFL) shl 8) or
|
||||||
|
(bytes[7].toLong() and 0xFFL)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun nextLong(bound: Long): Long {
|
||||||
|
require(bound > 0) { throw IllegalArgumentException("Bad Bound $bound") }
|
||||||
|
|
||||||
|
if (bound < Int.MAX_VALUE) {
|
||||||
|
return nextInt(bound.toInt()).toLong()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextPositiveLong() % bound
|
||||||
|
}
|
||||||
|
|
||||||
|
fun nextPositiveInt(): Int {
|
||||||
|
val value = nextInt()
|
||||||
|
return if (value > 0) {
|
||||||
|
value
|
||||||
|
} else {
|
||||||
|
-value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun nextPositiveLong(): Long {
|
||||||
|
val value = nextLong()
|
||||||
|
return if (value > 0) {
|
||||||
|
value
|
||||||
|
} else {
|
||||||
|
-value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalForeignApi::class)
|
||||||
|
actual fun nextBytes(output: ByteArray) {
|
||||||
|
if (output.isEmpty()) return
|
||||||
|
val fd = open("/dev/urandom", O_RDONLY)
|
||||||
|
if (fd == -1) {
|
||||||
|
throw IllegalStateException("Failed to open /dev/urandom")
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
output.usePinned { pinned ->
|
||||||
|
var bytesRead = 0
|
||||||
|
while (bytesRead < output.size) {
|
||||||
|
val result = read(fd, pinned.addressOf(bytesRead), (output.size - bytesRead).convert())
|
||||||
|
if (result <= 0) {
|
||||||
|
throw IllegalStateException("Failed to read from /dev/urandom")
|
||||||
|
}
|
||||||
|
bytesRead += result.toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
close(fd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.utils
|
||||||
|
|
||||||
|
actual class UnicodeNormalizer {
|
||||||
|
actual fun normalizeNFKC(input: String): String {
|
||||||
|
// NFKC normalization on Linux native without ICU
|
||||||
|
// For most Nostr use cases (NIP-05, etc.) ASCII-safe content works without normalization.
|
||||||
|
// Full NFKC support would require linking against ICU or a pure-Kotlin normalizer.
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.utils
|
||||||
|
|
||||||
|
actual class UriParser actual constructor(
|
||||||
|
uri: String,
|
||||||
|
) {
|
||||||
|
private val parsedScheme: String?
|
||||||
|
private val parsedHost: String?
|
||||||
|
private val parsedPort: Int?
|
||||||
|
private val parsedPath: String?
|
||||||
|
private val parsedQuery: String?
|
||||||
|
private val parsedFragment: String?
|
||||||
|
|
||||||
|
init {
|
||||||
|
var remaining = uri
|
||||||
|
|
||||||
|
// Parse scheme
|
||||||
|
val schemeEnd = remaining.indexOf("://")
|
||||||
|
if (schemeEnd >= 0) {
|
||||||
|
parsedScheme = remaining.substring(0, schemeEnd)
|
||||||
|
remaining = remaining.substring(schemeEnd + 3)
|
||||||
|
} else {
|
||||||
|
parsedScheme = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse fragment
|
||||||
|
val fragmentStart = remaining.indexOf('#')
|
||||||
|
if (fragmentStart >= 0) {
|
||||||
|
parsedFragment = remaining.substring(fragmentStart + 1)
|
||||||
|
remaining = remaining.substring(0, fragmentStart)
|
||||||
|
} else {
|
||||||
|
parsedFragment = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse query
|
||||||
|
val queryStart = remaining.indexOf('?')
|
||||||
|
if (queryStart >= 0) {
|
||||||
|
parsedQuery = remaining.substring(queryStart + 1)
|
||||||
|
remaining = remaining.substring(0, queryStart)
|
||||||
|
} else {
|
||||||
|
parsedQuery = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse host and port
|
||||||
|
val pathStart = remaining.indexOf('/')
|
||||||
|
val authority =
|
||||||
|
if (pathStart >= 0) {
|
||||||
|
val auth = remaining.substring(0, pathStart)
|
||||||
|
remaining = remaining.substring(pathStart)
|
||||||
|
auth
|
||||||
|
} else {
|
||||||
|
val auth = remaining
|
||||||
|
remaining = ""
|
||||||
|
auth
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove userinfo
|
||||||
|
val atIndex = authority.lastIndexOf('@')
|
||||||
|
val hostPort = if (atIndex >= 0) authority.substring(atIndex + 1) else authority
|
||||||
|
|
||||||
|
// Handle IPv6
|
||||||
|
if (hostPort.startsWith("[")) {
|
||||||
|
val bracketEnd = hostPort.indexOf(']')
|
||||||
|
if (bracketEnd >= 0) {
|
||||||
|
parsedHost = hostPort.substring(1, bracketEnd)
|
||||||
|
val afterBracket = hostPort.substring(bracketEnd + 1)
|
||||||
|
parsedPort =
|
||||||
|
if (afterBracket.startsWith(":")) {
|
||||||
|
afterBracket.substring(1).toIntOrNull()
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parsedHost = hostPort
|
||||||
|
parsedPort = null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val colonIndex = hostPort.lastIndexOf(':')
|
||||||
|
if (colonIndex >= 0) {
|
||||||
|
val potentialPort = hostPort.substring(colonIndex + 1).toIntOrNull()
|
||||||
|
if (potentialPort != null) {
|
||||||
|
parsedHost = hostPort.substring(0, colonIndex)
|
||||||
|
parsedPort = potentialPort
|
||||||
|
} else {
|
||||||
|
parsedHost = hostPort
|
||||||
|
parsedPort = null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parsedHost = hostPort.ifEmpty { null }
|
||||||
|
parsedPort = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedPath = remaining.ifEmpty { null }
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun scheme(): String? = parsedScheme
|
||||||
|
|
||||||
|
actual fun host(): String? = parsedHost
|
||||||
|
|
||||||
|
actual fun port(): Int? = parsedPort
|
||||||
|
|
||||||
|
actual fun path(): String? = parsedPath
|
||||||
|
|
||||||
|
actual fun queryParameterNames(): Set<String> {
|
||||||
|
val query = parsedQuery ?: return emptySet()
|
||||||
|
return query
|
||||||
|
.split('&')
|
||||||
|
.mapNotNull { param ->
|
||||||
|
val eqIndex = param.indexOf('=')
|
||||||
|
if (eqIndex >= 0) param.substring(0, eqIndex) else param
|
||||||
|
}.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun getQueryParameter(param: String): String? {
|
||||||
|
val query = parsedQuery ?: return null
|
||||||
|
return query
|
||||||
|
.split('&')
|
||||||
|
.firstOrNull { part ->
|
||||||
|
val eqIndex = part.indexOf('=')
|
||||||
|
if (eqIndex >= 0) part.substring(0, eqIndex) == param else part == param
|
||||||
|
}?.let { part ->
|
||||||
|
val eqIndex = part.indexOf('=')
|
||||||
|
if (eqIndex >= 0) part.substring(eqIndex + 1) else ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val fragments: Map<String, String> by lazy {
|
||||||
|
parsedFragment?.ifBlank { null }?.let { keyValuePair ->
|
||||||
|
keyValuePair.split('&').associate { paramValue ->
|
||||||
|
val parts = paramValue.split("=", limit = 2)
|
||||||
|
if (parts.size == 2) {
|
||||||
|
parts[0] to parts[1]
|
||||||
|
} else {
|
||||||
|
parts[0] to ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} ?: emptyMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun fragments(): Map<String, String> = fragments
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.utils
|
||||||
|
|
||||||
|
import net.thauvin.erik.urlencoder.UrlEncoderUtil
|
||||||
|
|
||||||
|
actual object UrlEncoder {
|
||||||
|
actual fun encode(value: String): String = UrlEncoderUtil.encode(value)
|
||||||
|
|
||||||
|
actual fun decode(value: String): String = UrlEncoderUtil.decode(value)
|
||||||
|
}
|
||||||
+319
@@ -0,0 +1,319 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.utils.cache
|
||||||
|
|
||||||
|
import kotlin.concurrent.AtomicReference
|
||||||
|
|
||||||
|
actual class LargeCache<K, V> : ICacheOperations<K, V> {
|
||||||
|
private val mapRef = AtomicReference(LinkedHashMap<K, V>())
|
||||||
|
|
||||||
|
private inline fun <R> withMap(block: (LinkedHashMap<K, V>) -> R): R = block(mapRef.value)
|
||||||
|
|
||||||
|
private inline fun mutate(block: (LinkedHashMap<K, V>) -> Unit) {
|
||||||
|
val copy = LinkedHashMap(mapRef.value)
|
||||||
|
block(copy)
|
||||||
|
mapRef.value = copy
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun keys(): Set<K> = withMap { LinkedHashSet(it.keys) }
|
||||||
|
|
||||||
|
actual fun values(): Iterable<V> = withMap { ArrayList(it.values) }
|
||||||
|
|
||||||
|
actual fun get(key: K): V? = withMap { it[key] }
|
||||||
|
|
||||||
|
actual fun remove(key: K): V? {
|
||||||
|
val current = mapRef.value
|
||||||
|
val value = current[key]
|
||||||
|
if (value != null) {
|
||||||
|
mutate { it.remove(key) }
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun isEmpty(): Boolean = withMap { it.isEmpty() }
|
||||||
|
|
||||||
|
actual fun clear() {
|
||||||
|
mapRef.value = LinkedHashMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun containsKey(key: K): Boolean = withMap { it.containsKey(key) }
|
||||||
|
|
||||||
|
actual fun put(
|
||||||
|
key: K,
|
||||||
|
value: V,
|
||||||
|
) {
|
||||||
|
mutate { it[key] = value }
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun getOrCreate(
|
||||||
|
key: K,
|
||||||
|
builder: (K) -> V,
|
||||||
|
): V {
|
||||||
|
val existing = get(key)
|
||||||
|
if (existing != null) return existing
|
||||||
|
val newObject = builder(key)
|
||||||
|
mutate { it[key] = newObject }
|
||||||
|
return get(key) ?: newObject
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun createIfAbsent(
|
||||||
|
key: K,
|
||||||
|
builder: (K) -> V,
|
||||||
|
): Boolean {
|
||||||
|
val existing = get(key)
|
||||||
|
if (existing != null) return false
|
||||||
|
val newObject = builder(key)
|
||||||
|
mutate { it[key] = newObject }
|
||||||
|
return get(key) != null
|
||||||
|
}
|
||||||
|
|
||||||
|
actual override fun size(): Int = withMap { it.size }
|
||||||
|
|
||||||
|
actual override fun forEach(consumer: ICacheBiConsumer<K, V>) {
|
||||||
|
withMap { map -> map.forEach { consumer.accept(it.key, it.value) } }
|
||||||
|
}
|
||||||
|
|
||||||
|
actual override fun filter(consumer: CacheCollectors.BiFilter<K, V>): List<V> = withMap { map -> map.filter { consumer.filter(it.key, it.value) }.values.toList() }
|
||||||
|
|
||||||
|
actual override fun filterIntoSet(consumer: CacheCollectors.BiFilter<K, V>): Set<V> = withMap { map -> map.filter { consumer.filter(it.key, it.value) }.values.toSet() }
|
||||||
|
|
||||||
|
actual override fun <R> map(consumer: CacheCollectors.BiNotNullMapper<K, V, R>): List<R> = withMap { map -> map.map { consumer.map(it.key, it.value) } }
|
||||||
|
|
||||||
|
actual override fun <R> mapNotNull(consumer: CacheCollectors.BiMapper<K, V, R?>): List<R> = withMap { map -> map.mapNotNull { consumer.map(it.key, it.value) } }
|
||||||
|
|
||||||
|
actual override fun <R> mapNotNullIntoSet(consumer: CacheCollectors.BiMapper<K, V, R?>): Set<R> = mapNotNull(consumer).toSet()
|
||||||
|
|
||||||
|
actual override fun <R> mapFlatten(consumer: CacheCollectors.BiMapper<K, V, Collection<R>?>): List<R> = withMap { map -> map.flatMap { entry -> consumer.map(entry.key, entry.value) ?: emptyList() } }
|
||||||
|
|
||||||
|
actual override fun <R> mapFlattenIntoSet(consumer: CacheCollectors.BiMapper<K, V, Collection<R>?>): Set<R> = mapFlatten(consumer).toSet()
|
||||||
|
|
||||||
|
actual override fun maxOrNullOf(
|
||||||
|
filter: CacheCollectors.BiFilter<K, V>,
|
||||||
|
comparator: Comparator<V>,
|
||||||
|
): V? =
|
||||||
|
withMap { map ->
|
||||||
|
var maxV: V? = null
|
||||||
|
map.forEach {
|
||||||
|
if (filter.filter(it.key, it.value)) {
|
||||||
|
if (maxV == null || comparator.compare(it.value, maxV) > 0) {
|
||||||
|
maxV = it.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
maxV
|
||||||
|
}
|
||||||
|
|
||||||
|
actual override fun sumOf(consumer: CacheCollectors.BiSumOf<K, V>): Int =
|
||||||
|
withMap { map ->
|
||||||
|
var sum = 0
|
||||||
|
map.forEach { sum += consumer.map(it.key, it.value) }
|
||||||
|
sum
|
||||||
|
}
|
||||||
|
|
||||||
|
actual override fun sumOfLong(consumer: CacheCollectors.BiSumOfLong<K, V>): Long =
|
||||||
|
withMap { map ->
|
||||||
|
var sum = 0L
|
||||||
|
map.forEach { sum += consumer.map(it.key, it.value) }
|
||||||
|
sum
|
||||||
|
}
|
||||||
|
|
||||||
|
actual override fun <R> groupBy(consumer: CacheCollectors.BiNotNullMapper<K, V, R>): Map<R, List<V>> =
|
||||||
|
withMap { map ->
|
||||||
|
val results = HashMap<R, ArrayList<V>>()
|
||||||
|
map.forEach {
|
||||||
|
val group = consumer.map(it.key, it.value)
|
||||||
|
results.getOrPut(group) { ArrayList() }.add(it.value)
|
||||||
|
}
|
||||||
|
results
|
||||||
|
}
|
||||||
|
|
||||||
|
actual override fun <R> countByGroup(consumer: CacheCollectors.BiNotNullMapper<K, V, R>): Map<R, Int> =
|
||||||
|
withMap { map ->
|
||||||
|
val results = HashMap<R, Int>()
|
||||||
|
map.forEach {
|
||||||
|
val group = consumer.map(it.key, it.value)
|
||||||
|
results[group] = (results[group] ?: 0) + 1
|
||||||
|
}
|
||||||
|
results
|
||||||
|
}
|
||||||
|
|
||||||
|
actual override fun <R> sumByGroup(
|
||||||
|
groupMap: CacheCollectors.BiNotNullMapper<K, V, R>,
|
||||||
|
sumOf: CacheCollectors.BiNotNullMapper<K, V, Long>,
|
||||||
|
): Map<R, Long> =
|
||||||
|
withMap { map ->
|
||||||
|
val results = HashMap<R, Long>()
|
||||||
|
map.forEach {
|
||||||
|
val group = groupMap.map(it.key, it.value)
|
||||||
|
results[group] = (results[group] ?: 0L) + sumOf.map(it.key, it.value)
|
||||||
|
}
|
||||||
|
results
|
||||||
|
}
|
||||||
|
|
||||||
|
actual override fun count(consumer: CacheCollectors.BiFilter<K, V>): Int = withMap { map -> map.count { consumer.filter(it.key, it.value) } }
|
||||||
|
|
||||||
|
actual override fun <T, U> associate(transform: (K, V) -> Pair<T, U>): Map<T, U> =
|
||||||
|
withMap { map ->
|
||||||
|
val results = LinkedHashMap<T, U>(map.size)
|
||||||
|
map.forEach {
|
||||||
|
val pair = transform(it.key, it.value)
|
||||||
|
results[pair.first] = pair.second
|
||||||
|
}
|
||||||
|
results
|
||||||
|
}
|
||||||
|
|
||||||
|
actual override fun <U> associateWith(transform: (K, V) -> U?): Map<K, U?> =
|
||||||
|
withMap { map ->
|
||||||
|
val results = LinkedHashMap<K, U?>(map.size)
|
||||||
|
map.forEach {
|
||||||
|
results[it.key] = transform(it.key, it.value)
|
||||||
|
}
|
||||||
|
results
|
||||||
|
}
|
||||||
|
|
||||||
|
actual override fun filter(
|
||||||
|
from: K,
|
||||||
|
to: K,
|
||||||
|
consumer: CacheCollectors.BiFilter<K, V>,
|
||||||
|
): List<V> = filter(consumer)
|
||||||
|
|
||||||
|
actual override fun filterIntoSet(
|
||||||
|
from: K,
|
||||||
|
to: K,
|
||||||
|
consumer: CacheCollectors.BiFilter<K, V>,
|
||||||
|
): Set<V> = filterIntoSet(consumer)
|
||||||
|
|
||||||
|
actual override fun <R> map(
|
||||||
|
from: K,
|
||||||
|
to: K,
|
||||||
|
consumer: CacheCollectors.BiNotNullMapper<K, V, R>,
|
||||||
|
): List<R> = map(consumer)
|
||||||
|
|
||||||
|
actual override fun <R> mapNotNull(
|
||||||
|
from: K,
|
||||||
|
to: K,
|
||||||
|
consumer: CacheCollectors.BiMapper<K, V, R?>,
|
||||||
|
): List<R> = mapNotNull(consumer)
|
||||||
|
|
||||||
|
actual override fun <R> mapNotNullIntoSet(
|
||||||
|
from: K,
|
||||||
|
to: K,
|
||||||
|
consumer: CacheCollectors.BiMapper<K, V, R?>,
|
||||||
|
): Set<R> = mapNotNullIntoSet(consumer)
|
||||||
|
|
||||||
|
actual override fun <R> mapFlatten(
|
||||||
|
from: K,
|
||||||
|
to: K,
|
||||||
|
consumer: CacheCollectors.BiMapper<K, V, Collection<R>?>,
|
||||||
|
): List<R> = mapFlatten(consumer)
|
||||||
|
|
||||||
|
actual override fun <R> mapFlattenIntoSet(
|
||||||
|
from: K,
|
||||||
|
to: K,
|
||||||
|
consumer: CacheCollectors.BiMapper<K, V, Collection<R>?>,
|
||||||
|
): Set<R> = mapFlattenIntoSet(consumer)
|
||||||
|
|
||||||
|
actual override fun maxOrNullOf(
|
||||||
|
from: K,
|
||||||
|
to: K,
|
||||||
|
filter: CacheCollectors.BiFilter<K, V>,
|
||||||
|
comparator: Comparator<V>,
|
||||||
|
): V? = maxOrNullOf(filter, comparator)
|
||||||
|
|
||||||
|
actual override fun sumOf(
|
||||||
|
from: K,
|
||||||
|
to: K,
|
||||||
|
consumer: CacheCollectors.BiSumOf<K, V>,
|
||||||
|
): Int = sumOf(consumer)
|
||||||
|
|
||||||
|
actual override fun sumOfLong(
|
||||||
|
from: K,
|
||||||
|
to: K,
|
||||||
|
consumer: CacheCollectors.BiSumOfLong<K, V>,
|
||||||
|
): Long = sumOfLong(consumer)
|
||||||
|
|
||||||
|
actual override fun <R> groupBy(
|
||||||
|
from: K,
|
||||||
|
to: K,
|
||||||
|
consumer: CacheCollectors.BiNotNullMapper<K, V, R>,
|
||||||
|
): Map<R, List<V>> = groupBy(consumer)
|
||||||
|
|
||||||
|
actual override fun <R> countByGroup(
|
||||||
|
from: K,
|
||||||
|
to: K,
|
||||||
|
consumer: CacheCollectors.BiNotNullMapper<K, V, R>,
|
||||||
|
): Map<R, Int> = countByGroup(consumer)
|
||||||
|
|
||||||
|
actual override fun <R> sumByGroup(
|
||||||
|
from: K,
|
||||||
|
to: K,
|
||||||
|
groupMap: CacheCollectors.BiNotNullMapper<K, V, R>,
|
||||||
|
sumOf: CacheCollectors.BiNotNullMapper<K, V, Long>,
|
||||||
|
): Map<R, Long> = sumByGroup(groupMap, sumOf)
|
||||||
|
|
||||||
|
actual override fun count(
|
||||||
|
from: K,
|
||||||
|
to: K,
|
||||||
|
consumer: CacheCollectors.BiFilter<K, V>,
|
||||||
|
): Int = count(consumer)
|
||||||
|
|
||||||
|
actual override fun <T, U> associate(
|
||||||
|
from: K,
|
||||||
|
to: K,
|
||||||
|
transform: (K, V) -> Pair<T, U>,
|
||||||
|
): Map<T, U> = associate(transform)
|
||||||
|
|
||||||
|
actual override fun <U> associateWith(
|
||||||
|
from: K,
|
||||||
|
to: K,
|
||||||
|
transform: (K, V) -> U?,
|
||||||
|
): Map<K, U?> = associateWith(transform)
|
||||||
|
|
||||||
|
actual override fun joinToString(
|
||||||
|
separator: CharSequence,
|
||||||
|
prefix: CharSequence,
|
||||||
|
postfix: CharSequence,
|
||||||
|
limit: Int,
|
||||||
|
truncated: CharSequence,
|
||||||
|
transform: ((K, V) -> CharSequence)?,
|
||||||
|
): String {
|
||||||
|
val buffer = StringBuilder()
|
||||||
|
buffer.append(prefix)
|
||||||
|
var count = 0
|
||||||
|
forEach { key, value ->
|
||||||
|
val str = if (transform != null) transform(key, value) else ""
|
||||||
|
if (str.isNotEmpty()) {
|
||||||
|
if (++count > 1) buffer.append(separator)
|
||||||
|
if (limit < 0 || count <= limit) {
|
||||||
|
when {
|
||||||
|
transform != null -> buffer.append(str)
|
||||||
|
else -> buffer.append("$key $value")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (limit >= 0 && count > limit) buffer.append(truncated)
|
||||||
|
buffer.append(postfix)
|
||||||
|
return buffer.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.utils.ciphers
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.utils.Log
|
||||||
|
import dev.whyoleg.cryptography.CryptographyProvider
|
||||||
|
import dev.whyoleg.cryptography.DelicateCryptographyApi
|
||||||
|
import dev.whyoleg.cryptography.algorithms.AES
|
||||||
|
|
||||||
|
actual class AESCBC actual constructor(
|
||||||
|
actual val keyBytes: ByteArray,
|
||||||
|
actual val iv: ByteArray,
|
||||||
|
) : NostrCipher {
|
||||||
|
private val cryptoProvider = CryptographyProvider.Default
|
||||||
|
private val aesCbc = cryptoProvider.get(AES.CBC)
|
||||||
|
|
||||||
|
private val keyDecoder =
|
||||||
|
aesCbc
|
||||||
|
.keyDecoder()
|
||||||
|
.decodeFromByteArrayBlocking(format = AES.Key.Format.RAW, keyBytes)
|
||||||
|
|
||||||
|
private fun cipher() = keyDecoder.cipher()
|
||||||
|
|
||||||
|
actual override fun name(): String = "aes-cbc"
|
||||||
|
|
||||||
|
@OptIn(DelicateCryptographyApi::class)
|
||||||
|
actual override fun encrypt(bytesToEncrypt: ByteArray): ByteArray =
|
||||||
|
with(cipher()) {
|
||||||
|
encryptWithIvBlocking(iv, bytesToEncrypt)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(DelicateCryptographyApi::class)
|
||||||
|
actual override fun decrypt(bytesToDecrypt: ByteArray): ByteArray =
|
||||||
|
with(cipher()) {
|
||||||
|
decryptWithIvBlocking(iv, bytesToDecrypt)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(DelicateCryptographyApi::class)
|
||||||
|
actual override fun decryptOrNull(bytesToDecrypt: ByteArray): ByteArray? =
|
||||||
|
try {
|
||||||
|
with(cipher()) {
|
||||||
|
decryptWithIvBlocking(iv, bytesToDecrypt)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w("AESCBC", "Failed to decrypt", e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.utils.ciphers
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.utils.Log
|
||||||
|
import dev.whyoleg.cryptography.CryptographyProvider
|
||||||
|
import dev.whyoleg.cryptography.DelicateCryptographyApi
|
||||||
|
import dev.whyoleg.cryptography.algorithms.AES
|
||||||
|
|
||||||
|
actual class AESGCM actual constructor(
|
||||||
|
actual val keyBytes: ByteArray,
|
||||||
|
actual val nonce: ByteArray,
|
||||||
|
) : NostrCipher {
|
||||||
|
private val provider = CryptographyProvider.Default
|
||||||
|
private val aesGcm = provider.get(AES.GCM)
|
||||||
|
|
||||||
|
private val keyDecoder =
|
||||||
|
aesGcm
|
||||||
|
.keyDecoder()
|
||||||
|
.decodeFromByteArrayBlocking(AES.Key.Format.RAW, keyBytes)
|
||||||
|
|
||||||
|
private fun cipher() = keyDecoder.cipher()
|
||||||
|
|
||||||
|
actual override fun name(): String = "aes-gcm"
|
||||||
|
|
||||||
|
@OptIn(DelicateCryptographyApi::class)
|
||||||
|
actual override fun encrypt(bytesToEncrypt: ByteArray): ByteArray =
|
||||||
|
with(cipher()) {
|
||||||
|
encryptWithIvBlocking(nonce, bytesToEncrypt)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(DelicateCryptographyApi::class)
|
||||||
|
actual override fun decrypt(bytesToDecrypt: ByteArray): ByteArray =
|
||||||
|
with(cipher()) {
|
||||||
|
decryptWithIvBlocking(nonce, bytesToDecrypt)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(DelicateCryptographyApi::class)
|
||||||
|
actual override fun decryptOrNull(bytesToDecrypt: ByteArray): ByteArray? =
|
||||||
|
try {
|
||||||
|
with(cipher()) {
|
||||||
|
decryptWithIvBlocking(nonce, bytesToDecrypt)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w("AESGCM", "Failed to decrypt", e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
+58
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.utils.diggest
|
||||||
|
|
||||||
|
import dev.whyoleg.cryptography.CryptographyProvider
|
||||||
|
import dev.whyoleg.cryptography.DelicateCryptographyApi
|
||||||
|
import dev.whyoleg.cryptography.algorithms.RIPEMD160
|
||||||
|
import dev.whyoleg.cryptography.algorithms.SHA1
|
||||||
|
import dev.whyoleg.cryptography.algorithms.SHA256
|
||||||
|
|
||||||
|
actual class DigestInstance actual constructor(
|
||||||
|
algorithm: String,
|
||||||
|
) {
|
||||||
|
private val cryptoProvider = CryptographyProvider.Default
|
||||||
|
|
||||||
|
private val hasher = cryptoProvider.get(digestForAlgorithm(algorithm)).hasher()
|
||||||
|
private val hashFunction = hasher.createHashFunction()
|
||||||
|
|
||||||
|
actual fun update(array: ByteArray) {
|
||||||
|
hashFunction.update(array)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun update(byte: Byte) {
|
||||||
|
hashFunction.update(byteArrayOf(byte))
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun digest(): ByteArray = hashFunction.hashToByteArray()
|
||||||
|
|
||||||
|
actual fun digest(input: ByteArray): ByteArray = hasher.hashBlocking(input)
|
||||||
|
|
||||||
|
@OptIn(DelicateCryptographyApi::class)
|
||||||
|
private fun digestForAlgorithm(algorithmName: String) =
|
||||||
|
when (algorithmName) {
|
||||||
|
"SHA-256" -> SHA256
|
||||||
|
"SHA-1" -> SHA1
|
||||||
|
"ripemd160" -> RIPEMD160
|
||||||
|
"keccak256" -> error("KECCAK-256 is not yet supported.")
|
||||||
|
else -> error("This message digest is not supported.")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.utils.mac
|
||||||
|
|
||||||
|
import io.github.andreypfau.kotlinx.crypto.HMac
|
||||||
|
import io.github.andreypfau.kotlinx.crypto.Sha256
|
||||||
|
import io.github.andreypfau.kotlinx.crypto.Sha512
|
||||||
|
|
||||||
|
actual class MacInstance actual constructor(
|
||||||
|
algorithm: String,
|
||||||
|
key: ByteArray,
|
||||||
|
) {
|
||||||
|
private var nativeHmac = HMac(digestForAlgorithm(algorithm), key)
|
||||||
|
|
||||||
|
actual fun init(
|
||||||
|
key: ByteArray,
|
||||||
|
algorithm: String,
|
||||||
|
) {
|
||||||
|
nativeHmac = HMac(digestForAlgorithm(algorithm), key)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun getMacLength(): Int = nativeHmac.macSize
|
||||||
|
|
||||||
|
actual fun update(array: ByteArray) {
|
||||||
|
nativeHmac.update(array)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun update(byte: Byte) {
|
||||||
|
nativeHmac.update(byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun doFinal(): ByteArray = nativeHmac.digest()
|
||||||
|
|
||||||
|
actual fun doFinal(
|
||||||
|
output: ByteArray,
|
||||||
|
offset: Int,
|
||||||
|
) {
|
||||||
|
nativeHmac.digest(output, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun digestForAlgorithm(algorithm: String) =
|
||||||
|
when (algorithm) {
|
||||||
|
"HmacSHA256" -> Sha256()
|
||||||
|
"HmacSHA512" -> Sha512()
|
||||||
|
else -> error("Algorithm is not yet supported.")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.utils.sha256
|
||||||
|
|
||||||
|
import dev.whyoleg.cryptography.CryptographyProvider
|
||||||
|
import dev.whyoleg.cryptography.algorithms.SHA256
|
||||||
|
|
||||||
|
actual fun sha256(data: ByteArray): ByteArray {
|
||||||
|
val provider = CryptographyProvider.Default
|
||||||
|
val hasher = provider.get(SHA256).hasher()
|
||||||
|
return hasher.hashBlocking(data)
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.utils
|
||||||
|
|
||||||
|
import platform.Foundation.NSDate
|
||||||
|
import platform.Foundation.timeIntervalSince1970
|
||||||
|
|
||||||
|
actual fun platform() = "macOS"
|
||||||
|
|
||||||
|
actual fun currentTimeSeconds(): Long = (NSDate().timeIntervalSince1970).toLong()
|
||||||
+1
-6
@@ -45,7 +45,6 @@ actual object EventHasherSerializer {
|
|||||||
add(kind)
|
add(kind)
|
||||||
addJsonArray {
|
addJsonArray {
|
||||||
tags.fastForEach { tag ->
|
tags.fastForEach { tag ->
|
||||||
// add(JsonArray(content = tag.map { JsonPrimitive(it) }))
|
|
||||||
addJsonArray { tag.fastForEach { add(it) } }
|
addJsonArray { tag.fastForEach { add(it) } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,10 +67,7 @@ actual object EventHasherSerializer {
|
|||||||
kind: Int,
|
kind: Int,
|
||||||
tags: Array<Array<String>>,
|
tags: Array<Array<String>>,
|
||||||
content: String,
|
content: String,
|
||||||
): ByteArray {
|
): ByteArray = makeJsonObjectForId(pubKey, createdAt, kind, tags, content).toString().encodeToByteArray()
|
||||||
// Also use makeJsonObjectForId(...) above. May change this if needed(for performance).
|
|
||||||
return makeJsonObjectForId(pubKey, createdAt, kind, tags, content).toString().encodeToByteArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
actual fun makeJsonForIdHashAndCheck(
|
actual fun makeJsonForIdHashAndCheck(
|
||||||
id: HexKey,
|
id: HexKey,
|
||||||
@@ -81,7 +77,6 @@ actual object EventHasherSerializer {
|
|||||||
tags: Array<Array<String>>,
|
tags: Array<Array<String>>,
|
||||||
content: String,
|
content: String,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
// Also use makeJsonObjectForId(...) above. May change byteArray encode method if needed.
|
|
||||||
val eventIdHash = sha256(makeJsonObjectForId(pubKey, createdAt, kind, tags, content).toString().encodeToByteArray())
|
val eventIdHash = sha256(makeJsonObjectForId(pubKey, createdAt, kind, tags, content).toString().encodeToByteArray())
|
||||||
return Hex.isEqual(id, eventIdHash)
|
return Hex.isEqual(id, eventIdHash)
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user