- Migrates Quartz from Android to CommonMain
- Fully converts OpenTimestamp Java codebase to Kotlin, migrating the sync and async HTTP call interfaces to OkHttp and coroutines - Redesigns parsing of relay commands, messages and filters for performance in Jackson. - Starts the use of KotlinX Serialization when speed is not a requirement - Migrates all Jackson field annotations to Kotlin Serialization - Migrates Regex use in Quarts to Kotlin's Regex class - Migrates Base64 from Android to Kotlin - Migrates UUID from Android/Java to Kotlin - Migrates LRUCache usage from Android/Java to Kotlin collections - Migrates all String to bytearray conversions to Kotlin methods - Migrates all System.arraycopy calls to kotlin native ones. - Separates parsing code from the data classes Companion objects - Exposes Rfc3986 normalizations to each platform. - Exposes URI parsing classes to each platform. - Exposes URL Encoders to each platform. - Exposes BigDecimal to each platform. - Exposes the Url Detector to each platform. - Exposes MacInstances to each platform - Exposes Diggest instances to each platform. - Exposes a BitSet to each platform. - Exposes GZip to each platform. - Exposes Secp256k1 to each platform. - Exposes SecureRandom to each platform. - Exposes Time in seconds to each platform. - Exposes the LargeCache to each platform. - Exposes AES CBC and AES GCM encryption/decryption to each platform - Migrate test assertions to Kotlin Tests - Exposes Address class to each platform because of the Parceleable - Creates our own ByteArrayOutputStream. - Removes Lock features inside the Bloomfilters because we don't need data consistency/ - Migrates UserMetadata parser from Jackson to Kotlin serialization - Removes the need for Static methods in each tag. - Adds an event template serializer - Adds KotlinX Datetime to migrate some of the date-based logs - Adds support for LibSodium in the JVM platform - Creates a shared test build for iOS targets - Fixes several usages of Reflection when serializing classes - Fixes a bug on loading RelayDB for the HintBloom filter test - Increases the Bloom filter space to better use hints in the app. - Removes support for iOS in x86 - Creates a Jackson mapper just for NIP-55, which stays in the Android build only. - Keeps the event store in the android build as well. - Removes @Syncronized tags in favor of Mutexes. - Improved sendAndWaitForResponse NostrClient method to properly account for returns from each relay. - Removes the need for GlobalScope and async calls in the downloadFirstEvent method. - Restructures the parser and serialization of the relay messages and commands for performance with Jackson - Removes the dependency on Jackson's error classes across the codebase. - Moves the hint to quote tag extension methods to their own packages. - Speeds up the generation of Bech32 addresses - Migrates NIP-6 and Blossom uploads to use Kotlin Serialization
This commit is contained in:
+45
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 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.nip01Core.core
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.JacksonMapper
|
||||
|
||||
actual object OptimizedJsonMapper {
|
||||
fun <T> runCatching(parsingAction: () -> T): T =
|
||||
try {
|
||||
parsingAction()
|
||||
// wraps jackson errors
|
||||
} catch (e: com.fasterxml.jackson.core.JsonParseException) {
|
||||
throw IllegalArgumentException(e.message, e)
|
||||
}
|
||||
|
||||
actual fun fromJson(json: String): Event = runCatching { JacksonMapper.fromJson(json) }
|
||||
|
||||
actual fun toJson(event: Event) = JacksonMapper.toJson(event)
|
||||
|
||||
actual fun fromJsonToTagArray(json: String): Array<Array<String>> = runCatching { JacksonMapper.fromJsonToTagArray(json) }
|
||||
|
||||
actual fun toJson(tags: Array<Array<String>>): String = JacksonMapper.toJson(tags)
|
||||
|
||||
actual inline fun <reified T : OptimizedSerializable> fromJsonTo(json: String): T = runCatching { JacksonMapper.fromJsonTo<T>(json) }
|
||||
|
||||
actual fun toJson(value: OptimizedSerializable): String = JacksonMapper.toJson(value)
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* 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.nip01Core.crypto
|
||||
|
||||
import com.fasterxml.jackson.core.JsonEncoding
|
||||
import com.fasterxml.jackson.core.JsonGenerator
|
||||
import com.fasterxml.jackson.core.JsonProcessingException
|
||||
import com.fasterxml.jackson.core.util.BufferRecycler
|
||||
import com.fasterxml.jackson.core.util.ByteArrayBuilder
|
||||
import com.fasterxml.jackson.databind.JsonMappingException
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeFactory
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.JacksonMapper
|
||||
import java.io.IOException
|
||||
|
||||
actual object EventHasherSerializer {
|
||||
fun makeJsonObjectForId(
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
kind: Int,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
): ArrayNode {
|
||||
val factory = JsonNodeFactory.instance
|
||||
return factory.arrayNode(6).apply {
|
||||
add(0)
|
||||
add(pubKey)
|
||||
add(createdAt)
|
||||
add(kind)
|
||||
add(
|
||||
factory.arrayNode(tags.size).apply {
|
||||
tags.forEach { tag ->
|
||||
add(
|
||||
factory.arrayNode(tag.size).apply { tag.forEach { add(it) } },
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
add(content)
|
||||
}
|
||||
}
|
||||
|
||||
actual fun makeJsonForId(
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
kind: Int,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
): String = JacksonMapper.toJson(makeJsonObjectForId(pubKey, createdAt, kind, tags, content))
|
||||
|
||||
actual fun fastMakeJsonForId(
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
kind: Int,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
): ByteArray {
|
||||
val br: BufferRecycler = JacksonMapper.mapper.factory._getBufferRecycler()
|
||||
try {
|
||||
ByteArrayBuilder(br).use { bb ->
|
||||
val generator = JacksonMapper.mapper.createGenerator(bb, JsonEncoding.UTF8)
|
||||
generator.enable(JsonGenerator.Feature.COMBINE_UNICODE_SURROGATES_IN_UTF8)
|
||||
generator.use {
|
||||
it.writeStartArray()
|
||||
it.writeNumber(0)
|
||||
it.writeString(pubKey)
|
||||
it.writeNumber(createdAt)
|
||||
it.writeNumber(kind)
|
||||
it.writeStartArray()
|
||||
tags.forEach { tag ->
|
||||
it.writeStartArray()
|
||||
tag.forEach { value ->
|
||||
it.writeString(value)
|
||||
}
|
||||
it.writeEndArray()
|
||||
}
|
||||
it.writeEndArray()
|
||||
it.writeString(content)
|
||||
it.writeEndArray()
|
||||
}
|
||||
|
||||
val result = bb.toByteArray()
|
||||
bb.release()
|
||||
return result
|
||||
}
|
||||
} catch (e: JsonProcessingException) {
|
||||
throw e
|
||||
} catch (e: IOException) {
|
||||
// shouldn't really happen, but is declared as possibility so:
|
||||
throw JsonMappingException.fromUnexpectedIOE(e)
|
||||
} finally {
|
||||
br.releaseToPool()
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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.nip01Core.jackson
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser
|
||||
import com.fasterxml.jackson.databind.DeserializationContext
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
|
||||
class EventDeserializer : StdDeserializer<Event>(Event::class.java) {
|
||||
override fun deserialize(
|
||||
jp: JsonParser,
|
||||
ctxt: DeserializationContext,
|
||||
): Event = EventManualDeserializer.fromJson(jp.codec.readTree(jp))
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 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.nip01Core.jackson
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.utils.EventFactory
|
||||
|
||||
class EventManualDeserializer {
|
||||
companion object {
|
||||
fun fromJson(jsonObject: JsonNode): Event =
|
||||
EventFactory.create(
|
||||
id = jsonObject.get("id").asText().intern(),
|
||||
pubKey = jsonObject.get("pubkey").asText().intern(),
|
||||
createdAt = jsonObject.get("created_at").asLong(),
|
||||
kind = jsonObject.get("kind").asInt(),
|
||||
tags =
|
||||
jsonObject.get("tags").toTypedArray {
|
||||
it.toTypedArray { s -> if (s.isNull) "" else s.asText().intern() }
|
||||
},
|
||||
content = jsonObject.get("content").asText(),
|
||||
sig = jsonObject.get("sig").asText(),
|
||||
)
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 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.nip01Core.jackson
|
||||
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeFactory
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
|
||||
class EventManualSerializer {
|
||||
companion object {
|
||||
fun assemble(
|
||||
id: HexKey,
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
kind: Int,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
sig: String,
|
||||
): ObjectNode {
|
||||
val factory = JsonNodeFactory.instance
|
||||
|
||||
return factory.objectNode().apply {
|
||||
put("id", id)
|
||||
put("pubkey", pubKey)
|
||||
put("created_at", createdAt)
|
||||
put("kind", kind)
|
||||
replace(
|
||||
"tags",
|
||||
factory.arrayNode(tags.size).apply {
|
||||
tags.forEach { tag ->
|
||||
add(
|
||||
factory.arrayNode(tag.size).apply { tag.forEach { add(it) } },
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
put("content", content)
|
||||
put("sig", sig)
|
||||
}
|
||||
}
|
||||
|
||||
fun toJson(
|
||||
id: HexKey,
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
kind: Int,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
sig: String,
|
||||
): String {
|
||||
val obj = assemble(id, pubKey, createdAt, kind, tags, content, sig)
|
||||
return JacksonMapper.mapper.writeValueAsString(obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
+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.nip01Core.jackson
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator
|
||||
import com.fasterxml.jackson.databind.SerializerProvider
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
|
||||
class EventSerializer : StdSerializer<Event>(Event::class.java) {
|
||||
override fun serialize(
|
||||
event: Event,
|
||||
gen: JsonGenerator,
|
||||
provider: SerializerProvider,
|
||||
) {
|
||||
gen.writeStartObject()
|
||||
gen.writeStringField("id", event.id)
|
||||
gen.writeStringField("pubkey", event.pubKey)
|
||||
gen.writeNumberField("created_at", event.createdAt)
|
||||
gen.writeNumberField("kind", event.kind)
|
||||
gen.writeArrayFieldStart("tags")
|
||||
for (i in event.tags.indices) {
|
||||
gen.writeStartArray()
|
||||
for (j in event.tags[i].indices) {
|
||||
gen.writeString(event.tags[i][j])
|
||||
}
|
||||
gen.writeEndArray()
|
||||
}
|
||||
gen.writeEndArray()
|
||||
gen.writeStringField("content", event.content)
|
||||
gen.writeStringField("sig", event.sig)
|
||||
gen.writeEndObject()
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* 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.nip01Core.jackson
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator
|
||||
import com.fasterxml.jackson.core.util.DefaultIndenter
|
||||
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter
|
||||
import com.fasterxml.jackson.core.util.Separators
|
||||
|
||||
class InliningTagArrayPrettyPrinter : DefaultPrettyPrinter {
|
||||
companion object {
|
||||
val MY_SEPARATORS =
|
||||
DEFAULT_SEPARATORS
|
||||
.withObjectFieldValueSpacing(Separators.Spacing.AFTER)
|
||||
}
|
||||
|
||||
init {
|
||||
indentArraysWith(DefaultIndenter(" ", "\n"))
|
||||
}
|
||||
|
||||
constructor(separators: Separators? = MY_SEPARATORS) : super(separators)
|
||||
|
||||
constructor(base: InliningTagArrayPrettyPrinter) : super(base)
|
||||
|
||||
override fun createInstance(): DefaultPrettyPrinter = InliningTagArrayPrettyPrinter(this)
|
||||
|
||||
override fun writeStartArray(g: JsonGenerator) {
|
||||
if (!_arrayIndenter.isInline) {
|
||||
++_nesting
|
||||
}
|
||||
g.writeRaw('[')
|
||||
}
|
||||
|
||||
override fun beforeArrayValues(g: JsonGenerator) {
|
||||
if (_nesting < 3) {
|
||||
_arrayIndenter.writeIndentation(g, _nesting)
|
||||
}
|
||||
}
|
||||
|
||||
override fun writeArrayValueSeparator(g: JsonGenerator) {
|
||||
g.writeRaw(_arrayValueSeparator)
|
||||
if (_nesting < 3) {
|
||||
_arrayIndenter.writeIndentation(g, _nesting)
|
||||
} else {
|
||||
g.writeRaw(' ')
|
||||
}
|
||||
}
|
||||
|
||||
override fun writeEndArray(
|
||||
g: JsonGenerator,
|
||||
nrOfValues: Int,
|
||||
) {
|
||||
if (!_arrayIndenter.isInline) {
|
||||
--_nesting
|
||||
}
|
||||
if (nrOfValues > 0) {
|
||||
if (_nesting < 2) {
|
||||
_arrayIndenter.writeIndentation(g, _nesting)
|
||||
}
|
||||
}
|
||||
g.writeRaw(']')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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.nip01Core.jackson
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
|
||||
inline fun <reified R> JsonNode.toTypedArray(transform: (JsonNode) -> R): Array<R> = Array(size()) { transform(get(it)) }
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* 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.nip01Core.jackson
|
||||
|
||||
import com.fasterxml.jackson.core.json.JsonReadFeature
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.OptimizedSerializable
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArray
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.MessageDeserializer
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.MessageSerializer
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.Command
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.CommandDeserializer
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.CommandSerializer
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.FilterDeserializer
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.FilterSerializer
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.EventTemplateDeserializer
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.EventTemplateSerializer
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerMessage
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequest
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponse
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.jackson.BunkerMessageDeserializer
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.jackson.BunkerRequestDeserializer
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.jackson.BunkerRequestSerializer
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.jackson.BunkerResponseDeserializer
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.jackson.BunkerResponseSerializer
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.Request
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.Response
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.jackson.RequestDeserializer
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.jackson.ResponseDeserializer
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.rumors.Rumor
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.rumors.jackson.RumorDeserializer
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.rumors.jackson.RumorSerializer
|
||||
import java.io.InputStream
|
||||
import kotlin.jvm.java
|
||||
|
||||
class JacksonMapper {
|
||||
companion object {
|
||||
val defaultPrettyPrinter = InliningTagArrayPrettyPrinter()
|
||||
|
||||
val mapper =
|
||||
jacksonObjectMapper()
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
.enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature())
|
||||
.setDefaultPrettyPrinter(defaultPrettyPrinter)
|
||||
.registerModule(
|
||||
SimpleModule()
|
||||
// nip 01
|
||||
.addSerializer(Event::class.java, EventSerializer())
|
||||
.addDeserializer(Event::class.java, EventDeserializer())
|
||||
.addSerializer(Filter::class.java, FilterSerializer())
|
||||
.addDeserializer(Filter::class.java, FilterDeserializer())
|
||||
.addSerializer(Message::class.java, MessageSerializer())
|
||||
.addDeserializer(Message::class.java, MessageDeserializer())
|
||||
.addSerializer(Command::class.java, CommandSerializer())
|
||||
.addDeserializer(Command::class.java, CommandDeserializer())
|
||||
.addDeserializer(TagArray::class.java, TagArrayDeserializer())
|
||||
.addSerializer(TagArray::class.java, TagArraySerializer())
|
||||
.addDeserializer(EventTemplate::class.java, EventTemplateDeserializer())
|
||||
.addSerializer(EventTemplate::class.java, EventTemplateSerializer())
|
||||
// nip 59
|
||||
.addSerializer(Rumor::class.java, RumorSerializer())
|
||||
.addDeserializer(Rumor::class.java, RumorDeserializer())
|
||||
// nip 47
|
||||
.addDeserializer(Response::class.java, ResponseDeserializer())
|
||||
.addDeserializer(Request::class.java, RequestDeserializer())
|
||||
// nip 46
|
||||
.addDeserializer(BunkerMessage::class.java, BunkerMessageDeserializer())
|
||||
.addSerializer(BunkerRequest::class.java, BunkerRequestSerializer())
|
||||
.addDeserializer(BunkerRequest::class.java, BunkerRequestDeserializer())
|
||||
.addSerializer(BunkerResponse::class.java, BunkerResponseSerializer())
|
||||
.addDeserializer(BunkerResponse::class.java, BunkerResponseDeserializer()),
|
||||
)
|
||||
|
||||
fun fromJson(json: String): Event = mapper.readValue<Event>(json)
|
||||
|
||||
fun fromJson(json: JsonNode): Event = EventManualDeserializer.fromJson(json)
|
||||
|
||||
fun fromJsonToTagArray(json: String): Array<Array<String>> = mapper.readValue<Array<Array<String>>>(json)
|
||||
|
||||
inline fun <reified T : OptimizedSerializable> fromJsonTo(json: String): T = mapper.readValue<T>(json)
|
||||
|
||||
inline fun <reified T : OptimizedSerializable> fromJsonTo(json: InputStream): T = mapper.readValue<T>(json)
|
||||
|
||||
fun toJson(event: Event): String = EventManualSerializer.toJson(event.id, event.pubKey, event.createdAt, event.kind, event.tags, event.content, event.sig)
|
||||
|
||||
fun toJson(event: ArrayNode): String = mapper.writeValueAsString(event)
|
||||
|
||||
fun toJson(event: ObjectNode?): String = mapper.writeValueAsString(event)
|
||||
|
||||
fun toJson(value: OptimizedSerializable): String = mapper.writeValueAsString(value)
|
||||
|
||||
fun toJson(tags: Array<Array<String>>): String = mapper.writeValueAsString(tags)
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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.nip01Core.jackson
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser
|
||||
import com.fasterxml.jackson.databind.DeserializationContext
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
|
||||
|
||||
class TagArrayDeserializer : StdDeserializer<Array<Array<String>>>(Array<Array<String>>::class.java) {
|
||||
override fun deserialize(
|
||||
jp: JsonParser,
|
||||
ctxt: DeserializationContext,
|
||||
): Array<Array<String>> = TagArrayManualDeserializer.fromJson(jp.codec.readTree(jp))
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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.nip01Core.jackson
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
|
||||
class TagArrayManualDeserializer {
|
||||
companion object {
|
||||
fun fromJson(jsonObject: JsonNode): Array<Array<String>> =
|
||||
jsonObject.toTypedArray {
|
||||
it.toTypedArray { s -> if (s.isNull) "" else s.asText().intern() }
|
||||
}
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 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.nip01Core.jackson
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator
|
||||
import com.fasterxml.jackson.databind.SerializerProvider
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer
|
||||
|
||||
class TagArraySerializer : StdSerializer<Array<Array<String>>>(Array<Array<String>>::class.java) {
|
||||
override fun serialize(
|
||||
tags: Array<Array<String>>,
|
||||
gen: JsonGenerator,
|
||||
provider: SerializerProvider,
|
||||
) {
|
||||
gen.writeStartArray()
|
||||
for (i in tags.indices) {
|
||||
gen.writeStartArray()
|
||||
for (j in tags[i].indices) {
|
||||
gen.writeString(tags[i][j])
|
||||
}
|
||||
gen.writeEndArray()
|
||||
}
|
||||
gen.writeEndArray()
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* 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.nip01Core.relay.commands.toClient
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser
|
||||
import com.fasterxml.jackson.core.JsonToken
|
||||
import com.fasterxml.jackson.databind.DeserializationContext
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.EventManualDeserializer
|
||||
|
||||
class MessageDeserializer : StdDeserializer<Message>(Message::class.java) {
|
||||
override fun deserialize(
|
||||
jp: JsonParser,
|
||||
ctxt: DeserializationContext,
|
||||
): Message? {
|
||||
// Expect to start with a JSON array token
|
||||
if (jp.currentToken != JsonToken.START_ARRAY) {
|
||||
ctxt.reportWrongTokenException(this, JsonToken.START_ARRAY, "Expected START_ARRAY token")
|
||||
}
|
||||
|
||||
val type = jp.nextTextValue()
|
||||
val message =
|
||||
when (type) {
|
||||
EventMessage.LABEL -> {
|
||||
val subId = jp.nextTextValue()
|
||||
jp.nextToken()
|
||||
val event: JsonNode = jp.codec.readTree(jp)
|
||||
|
||||
EventMessage(
|
||||
subId = subId,
|
||||
event = EventManualDeserializer.fromJson(event),
|
||||
)
|
||||
}
|
||||
EoseMessage.LABEL ->
|
||||
EoseMessage(
|
||||
subId = jp.nextTextValue(),
|
||||
)
|
||||
NoticeMessage.LABEL ->
|
||||
NoticeMessage(
|
||||
message = jp.nextTextValue(),
|
||||
)
|
||||
OkMessage.LABEL ->
|
||||
OkMessage(
|
||||
eventId = jp.nextTextValue(),
|
||||
success = jp.nextBooleanValue(),
|
||||
message = jp.nextTextValue() ?: "",
|
||||
)
|
||||
AuthMessage.LABEL ->
|
||||
AuthMessage(
|
||||
challenge = jp.nextTextValue(),
|
||||
)
|
||||
NotifyMessage.LABEL ->
|
||||
NotifyMessage(
|
||||
message = jp.nextTextValue(),
|
||||
)
|
||||
ClosedMessage.LABEL ->
|
||||
ClosedMessage(
|
||||
subId = jp.nextTextValue(),
|
||||
message = jp.nextTextValue(),
|
||||
)
|
||||
else -> {
|
||||
throw IllegalArgumentException("Message $type is not supported")
|
||||
}
|
||||
}
|
||||
|
||||
if (jp.currentToken != JsonToken.END_ARRAY) {
|
||||
// cleaning out other array elements
|
||||
while (jp.nextToken() != JsonToken.END_ARRAY) {
|
||||
// clears out
|
||||
}
|
||||
}
|
||||
|
||||
return message
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* 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.nip01Core.relay.commands.toClient
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator
|
||||
import com.fasterxml.jackson.databind.SerializerProvider
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.EventSerializer
|
||||
|
||||
class MessageSerializer : StdSerializer<Message>(Message::class.java) {
|
||||
val eventSerializer = EventSerializer()
|
||||
|
||||
override fun serialize(
|
||||
msg: Message,
|
||||
gen: JsonGenerator,
|
||||
provider: SerializerProvider,
|
||||
) {
|
||||
gen.writeStartArray()
|
||||
gen.writeString(msg.label())
|
||||
|
||||
when (msg) {
|
||||
is EventMessage -> {
|
||||
gen.writeString(msg.subId)
|
||||
eventSerializer.serialize(msg.event, gen, provider)
|
||||
}
|
||||
|
||||
is NoticeMessage -> {
|
||||
gen.writeString(msg.message)
|
||||
}
|
||||
|
||||
is OkMessage -> {
|
||||
gen.writeString(msg.eventId)
|
||||
gen.writeString(msg.success.toString())
|
||||
if (msg.message.isNotBlank()) {
|
||||
gen.writeString(msg.message)
|
||||
}
|
||||
}
|
||||
|
||||
is AuthMessage -> {
|
||||
gen.writeString(msg.challenge)
|
||||
}
|
||||
|
||||
is NotifyMessage -> {
|
||||
gen.writeString(msg.message)
|
||||
}
|
||||
|
||||
is ClosedMessage -> {
|
||||
gen.writeString(msg.subId)
|
||||
gen.writeString(msg.message)
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
|
||||
gen.writeEndArray()
|
||||
}
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* 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.nip01Core.relay.commands.toRelay
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser
|
||||
import com.fasterxml.jackson.core.JsonToken
|
||||
import com.fasterxml.jackson.databind.DeserializationContext
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.EventManualDeserializer
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.ManualFilterDeserializer
|
||||
import com.vitorpamplona.quartz.nip42RelayAuth.RelayAuthEvent
|
||||
|
||||
class CommandDeserializer : StdDeserializer<Command>(Command::class.java) {
|
||||
override fun deserialize(
|
||||
jp: JsonParser,
|
||||
ctxt: DeserializationContext,
|
||||
): Command? {
|
||||
// Expect to start with a JSON array token
|
||||
if (jp.currentToken != JsonToken.START_ARRAY) {
|
||||
ctxt.reportWrongTokenException(this, JsonToken.START_ARRAY, "Expected START_ARRAY token")
|
||||
}
|
||||
|
||||
val type = jp.nextTextValue()
|
||||
val message =
|
||||
when (type) {
|
||||
ReqCmd.LABEL -> {
|
||||
val subId = jp.nextTextValue()
|
||||
val filters = mutableListOf<Filter>()
|
||||
|
||||
while (jp.nextToken() != JsonToken.END_ARRAY) {
|
||||
val filterObj: ObjectNode = jp.codec.readTree(jp)
|
||||
val filter = ManualFilterDeserializer.fromJson(filterObj)
|
||||
filters.add(filter)
|
||||
}
|
||||
|
||||
ReqCmd(
|
||||
subId = jp.nextTextValue(),
|
||||
filters = filters,
|
||||
)
|
||||
}
|
||||
|
||||
CountCmd.LABEL -> {
|
||||
val subId = jp.nextTextValue()
|
||||
val filters = mutableListOf<Filter>()
|
||||
|
||||
while (jp.nextToken() != JsonToken.END_ARRAY) {
|
||||
val filterObj: ObjectNode = jp.codec.readTree(jp)
|
||||
val filter = ManualFilterDeserializer.fromJson(filterObj)
|
||||
filters.add(filter)
|
||||
}
|
||||
|
||||
CountCmd(
|
||||
subId = subId,
|
||||
filters = filters,
|
||||
)
|
||||
}
|
||||
|
||||
EventCmd.LABEL -> {
|
||||
jp.nextToken()
|
||||
val event: JsonNode = jp.codec.readTree(jp)
|
||||
|
||||
EventCmd(
|
||||
event = EventManualDeserializer.fromJson(event),
|
||||
)
|
||||
}
|
||||
|
||||
CloseCmd.LABEL ->
|
||||
CloseCmd(
|
||||
subId = jp.nextTextValue(),
|
||||
)
|
||||
|
||||
AuthCmd.LABEL -> {
|
||||
jp.nextToken()
|
||||
val event: JsonNode = jp.codec.readTree(jp)
|
||||
AuthCmd(
|
||||
event = EventManualDeserializer.fromJson(event) as RelayAuthEvent,
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
throw IllegalArgumentException("Message $type is not supported")
|
||||
}
|
||||
}
|
||||
|
||||
if (jp.currentToken != JsonToken.END_ARRAY) {
|
||||
// cleaning out other array elements
|
||||
while (jp.nextToken() != JsonToken.END_ARRAY) {
|
||||
// clears out
|
||||
}
|
||||
}
|
||||
|
||||
return message
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 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.nip01Core.relay.commands.toRelay
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator
|
||||
import com.fasterxml.jackson.databind.SerializerProvider
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.EventSerializer
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.FilterSerializer
|
||||
|
||||
class CommandSerializer : StdSerializer<Command>(Command::class.java) {
|
||||
val eventSerializer = EventSerializer()
|
||||
val filterSerializer = FilterSerializer()
|
||||
|
||||
override fun serialize(
|
||||
cmd: Command,
|
||||
gen: JsonGenerator,
|
||||
provider: SerializerProvider,
|
||||
) {
|
||||
gen.writeStartArray()
|
||||
gen.writeString(cmd.label())
|
||||
|
||||
when (cmd) {
|
||||
is ReqCmd -> {
|
||||
gen.writeString(cmd.subId)
|
||||
cmd.filters.forEach {
|
||||
filterSerializer.serialize(it, gen, provider)
|
||||
}
|
||||
}
|
||||
|
||||
is EventCmd -> {
|
||||
eventSerializer.serialize(cmd.event, gen, provider)
|
||||
}
|
||||
|
||||
is CloseCmd -> {
|
||||
gen.writeString(cmd.subId)
|
||||
}
|
||||
|
||||
is AuthCmd -> {
|
||||
eventSerializer.serialize(cmd.event, gen, provider)
|
||||
}
|
||||
|
||||
is CountCmd -> {
|
||||
gen.writeString(cmd.subId)
|
||||
cmd.filters.forEach {
|
||||
filterSerializer.serialize(it, gen, provider)
|
||||
}
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
|
||||
gen.writeEndArray()
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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.nip01Core.relay.filters
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser
|
||||
import com.fasterxml.jackson.databind.DeserializationContext
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode
|
||||
|
||||
class FilterDeserializer : StdDeserializer<Filter>(Filter::class.java) {
|
||||
override fun deserialize(
|
||||
jp: JsonParser,
|
||||
ctxt: DeserializationContext,
|
||||
): Filter = ManualFilterDeserializer.fromJson(jp.codec.readTree(jp))
|
||||
}
|
||||
|
||||
class ManualFilterDeserializer {
|
||||
companion object {
|
||||
fun fromJson(jsonObject: ObjectNode): Filter {
|
||||
val tags = mutableListOf<String>()
|
||||
jsonObject.fieldNames().forEach {
|
||||
if (it.startsWith("#")) {
|
||||
tags.add(it.substring(1))
|
||||
}
|
||||
}
|
||||
|
||||
return Filter(
|
||||
ids = jsonObject.get("ids").map { it.asText() },
|
||||
authors = jsonObject.get("authors").map { it.asText() },
|
||||
kinds = jsonObject.get("kinds").map { it.asInt() },
|
||||
tags = tags.associateWith { jsonObject.get(it).map { it.asText() } },
|
||||
since = jsonObject.get("since").asLong(),
|
||||
until = jsonObject.get("until").asLong(),
|
||||
limit = jsonObject.get("limit").asInt(),
|
||||
search = jsonObject.get("search").asText(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 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.nip01Core.relay.filters
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator
|
||||
import com.fasterxml.jackson.databind.SerializerProvider
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer
|
||||
|
||||
class FilterSerializer : StdSerializer<Filter>(Filter::class.java) {
|
||||
override fun serialize(
|
||||
filter: Filter,
|
||||
gen: JsonGenerator,
|
||||
provider: SerializerProvider,
|
||||
) {
|
||||
gen.writeStartObject()
|
||||
|
||||
filter.ids?.run {
|
||||
gen.writeArrayFieldStart("ids")
|
||||
for (i in indices) {
|
||||
gen.writeString(this[i])
|
||||
}
|
||||
gen.writeEndArray()
|
||||
}
|
||||
|
||||
filter.authors?.run {
|
||||
gen.writeArrayFieldStart("authors")
|
||||
for (i in indices) {
|
||||
gen.writeString(this[i])
|
||||
}
|
||||
gen.writeEndArray()
|
||||
}
|
||||
|
||||
filter.kinds?.run {
|
||||
gen.writeArrayFieldStart("kinds")
|
||||
for (i in indices) {
|
||||
gen.writeNumber(this[i])
|
||||
}
|
||||
gen.writeEndArray()
|
||||
}
|
||||
|
||||
filter.tags?.run {
|
||||
entries.forEach { kv ->
|
||||
gen.writeArrayFieldStart("#${kv.key}")
|
||||
for (i in kv.value.indices) {
|
||||
gen.writeString(kv.value[i])
|
||||
}
|
||||
gen.writeEndArray()
|
||||
}
|
||||
}
|
||||
|
||||
filter.since?.run { gen.writeNumberField("since", this) }
|
||||
filter.until?.run { gen.writeNumberField("until", this) }
|
||||
filter.limit?.run { gen.writeNumberField("limit", this) }
|
||||
filter.search?.run { gen.writeStringField("search", this) }
|
||||
|
||||
gen.writeEndObject()
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* 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.nip01Core.relay.sockets.okhttp
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.sockets.WebSocket
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.sockets.WebSocketListener
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.sockets.WebsocketBuilder
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import okhttp3.WebSocket as OkHttpWebSocket
|
||||
import okhttp3.WebSocketListener as OkHttpWebSocketListener
|
||||
|
||||
class BasicOkHttpWebSocket(
|
||||
val url: NormalizedRelayUrl,
|
||||
val httpClient: (NormalizedRelayUrl) -> OkHttpClient,
|
||||
val out: WebSocketListener,
|
||||
) : WebSocket {
|
||||
private var socket: OkHttpWebSocket? = null
|
||||
|
||||
override fun needsReconnect() = socket == null
|
||||
|
||||
override fun connect() {
|
||||
val request = Request.Builder().url(url.url).build()
|
||||
|
||||
val listener =
|
||||
object : OkHttpWebSocketListener() {
|
||||
override fun onOpen(
|
||||
webSocket: OkHttpWebSocket,
|
||||
response: Response,
|
||||
) = out.onOpen(
|
||||
(response.receivedResponseAtMillis - response.sentRequestAtMillis).toInt(),
|
||||
response.headers["Sec-WebSocket-Extensions"]?.contains("permessage-deflate") ?: false,
|
||||
)
|
||||
|
||||
override fun onMessage(
|
||||
webSocket: OkHttpWebSocket,
|
||||
text: String,
|
||||
) = out.onMessage(text)
|
||||
|
||||
override fun onClosing(
|
||||
webSocket: OkHttpWebSocket,
|
||||
code: Int,
|
||||
reason: String,
|
||||
) = out.onClosing(code, reason)
|
||||
|
||||
override fun onClosed(
|
||||
webSocket: OkHttpWebSocket,
|
||||
code: Int,
|
||||
reason: String,
|
||||
) = out.onClosed(code, reason)
|
||||
|
||||
override fun onFailure(
|
||||
webSocket: OkHttpWebSocket,
|
||||
t: Throwable,
|
||||
response: Response?,
|
||||
) = out.onFailure(t, response?.code, response?.message)
|
||||
}
|
||||
|
||||
socket = httpClient(url).newWebSocket(request, listener)
|
||||
}
|
||||
|
||||
override fun disconnect() {
|
||||
socket?.cancel()
|
||||
socket = null
|
||||
}
|
||||
|
||||
override fun send(msg: String): Boolean = socket?.send(msg) ?: false
|
||||
|
||||
class Builder(
|
||||
val httpClient: (NormalizedRelayUrl) -> OkHttpClient,
|
||||
) : WebsocketBuilder {
|
||||
// Called when connecting.
|
||||
override fun build(
|
||||
url: NormalizedRelayUrl,
|
||||
out: WebSocketListener,
|
||||
) = BasicOkHttpWebSocket(url, httpClient, out)
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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.nip01Core.signers
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser
|
||||
import com.fasterxml.jackson.databind.DeserializationContext
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
|
||||
class EventTemplateDeserializer : StdDeserializer<EventTemplate<Event>>(EventTemplate::class.java) {
|
||||
override fun deserialize(
|
||||
jp: JsonParser,
|
||||
ctxt: DeserializationContext,
|
||||
): EventTemplate<Event> = EventTemplateManualDeserializer.fromJson(jp.codec.readTree(jp))
|
||||
}
|
||||
+40
@@ -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.nip01Core.signers
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.toTypedArray
|
||||
|
||||
class EventTemplateManualDeserializer {
|
||||
companion object {
|
||||
fun fromJson(jsonObject: JsonNode): EventTemplate<Event> =
|
||||
EventTemplate(
|
||||
createdAt = jsonObject.get("created_at").asLong(),
|
||||
kind = jsonObject.get("kind").asInt(),
|
||||
tags =
|
||||
jsonObject.get("tags").toTypedArray {
|
||||
it.toTypedArray { s -> if (s.isNull) "" else s.asText().intern() }
|
||||
},
|
||||
content = jsonObject.get("content").asText(),
|
||||
)
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 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.nip01Core.signers
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator
|
||||
import com.fasterxml.jackson.databind.SerializerProvider
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer
|
||||
|
||||
class EventTemplateSerializer : StdSerializer<EventTemplate<*>>(EventTemplate::class.java) {
|
||||
override fun serialize(
|
||||
event: EventTemplate<*>,
|
||||
gen: JsonGenerator,
|
||||
provider: SerializerProvider,
|
||||
) {
|
||||
gen.writeStartObject()
|
||||
gen.writeNumberField("created_at", event.createdAt)
|
||||
gen.writeNumberField("kind", event.kind)
|
||||
gen.writeArrayFieldStart("tags")
|
||||
for (i in event.tags.indices) {
|
||||
gen.writeStartArray()
|
||||
for (j in event.tags[i].indices) {
|
||||
gen.writeString(event.tags[i][j])
|
||||
}
|
||||
gen.writeEndArray()
|
||||
}
|
||||
gen.writeEndArray()
|
||||
gen.writeStringField("content", event.content)
|
||||
gen.writeEndObject()
|
||||
}
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* 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.nip03Timestamp.okhttp
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.JacksonMapper
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.BitcoinExplorer
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.BlockHeader
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.OtsBlockHeightCache
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.UrlException
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.coroutines.executeAsync
|
||||
|
||||
class OkHttpBitcoinExplorer(
|
||||
val baseUrl: () -> String,
|
||||
val client: OkHttpClient,
|
||||
val cache: OtsBlockHeightCache,
|
||||
) : BitcoinExplorer {
|
||||
/**
|
||||
* Retrieve the block information from the block hash.
|
||||
*
|
||||
* @param hash Hash of the block.
|
||||
* @return the blockheader of the hash
|
||||
* @throws Exception desc
|
||||
*/
|
||||
override suspend fun block(hash: String): BlockHeader {
|
||||
cache.getHeader(hash)?.let {
|
||||
return it
|
||||
}
|
||||
|
||||
val baseAPI = baseUrl()
|
||||
val url = "$baseAPI/block/$hash"
|
||||
|
||||
val request =
|
||||
Request
|
||||
.Builder()
|
||||
.header("Accept", "application/json")
|
||||
.url(url)
|
||||
.get()
|
||||
.build()
|
||||
|
||||
return client.newCall(request).execute().use {
|
||||
if (it.isSuccessful) {
|
||||
Log.d("OkHttpBlockstreamExplorer", "$baseAPI/block/$hash")
|
||||
|
||||
val jsonObject = JacksonMapper.mapper.readTree(it.body.string())
|
||||
|
||||
val merkleRoot = jsonObject["merkle_root"].asText()
|
||||
val time = jsonObject["timestamp"].asInt().toString()
|
||||
val blockHeader = BlockHeader(merkleRoot, hash, time)
|
||||
cache.putHeader(hash, blockHeader)
|
||||
blockHeader
|
||||
} else {
|
||||
throw UrlException(
|
||||
"Couldn't open $url: " + it.message + " " + it.code,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the block hash from the block height.
|
||||
*
|
||||
* @param height Height of the block.
|
||||
* @return the hash of the block at height height
|
||||
* @throws Exception desc
|
||||
*/
|
||||
@Throws(Exception::class)
|
||||
override suspend fun blockHash(height: Int): String {
|
||||
cache.getHeight(height)?.let {
|
||||
return it
|
||||
}
|
||||
|
||||
val baseAPI = baseUrl()
|
||||
val url = "$baseAPI/block-height/$height"
|
||||
|
||||
val request =
|
||||
Request
|
||||
.Builder()
|
||||
.url(url)
|
||||
.get()
|
||||
.build()
|
||||
|
||||
return client.newCall(request).executeAsync().use {
|
||||
if (it.isSuccessful) {
|
||||
val blockHash = it.body.string()
|
||||
|
||||
Log.d("OkHttpBlockstreamExplorer", "$url $blockHash")
|
||||
|
||||
cache.putHeight(height, blockHash)
|
||||
blockHash
|
||||
} else {
|
||||
throw UrlException("Couldn't open $url: " + it.message + " " + it.code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
// doesn't accept Tor
|
||||
const val BLOCKSTREAM_API_URL = "https://blockstream.info/api"
|
||||
|
||||
// accepts Tor
|
||||
const val MEMPOOL_API_URL = "https://mempool.space/api/"
|
||||
}
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* 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.nip03Timestamp.okhttp
|
||||
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.RemoteCalendar
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.Timestamp
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.CommitmentNotFoundException
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.ExceededSizeException
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.UrlException
|
||||
import com.vitorpamplona.quartz.utils.Hex
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okhttp3.coroutines.executeAsync
|
||||
|
||||
/**
|
||||
* Class representing remote calendar server interface.
|
||||
*/
|
||||
class OkHttpCalendar(
|
||||
val okHttpClient: (url: String) -> OkHttpClient,
|
||||
) : RemoteCalendar {
|
||||
override suspend fun submit(
|
||||
url: String,
|
||||
digest: ByteArray,
|
||||
): Timestamp {
|
||||
val url = "$url/digest"
|
||||
|
||||
val mediaType = "application/x-www-form-urlencoded; charset=utf-8".toMediaType()
|
||||
val requestBody = digest.toRequestBody(mediaType)
|
||||
|
||||
val request =
|
||||
Request
|
||||
.Builder()
|
||||
.header("Accept", "application/vnd.opentimestamps.v1")
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.url(url)
|
||||
.post(requestBody)
|
||||
.build()
|
||||
|
||||
val client = okHttpClient(url)
|
||||
return client.newCall(request).executeAsync().use {
|
||||
if (it.isSuccessful) {
|
||||
val ctx =
|
||||
StreamDeserializationContext(
|
||||
it.body.bytes(),
|
||||
)
|
||||
Timestamp.deserialize(ctx, digest)
|
||||
} else {
|
||||
throw UrlException(
|
||||
"Failed to open $url",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getTimestamp(
|
||||
url: String,
|
||||
commitment: ByteArray,
|
||||
): Timestamp =
|
||||
try {
|
||||
val url = url + "/timestamp/" + Hex.encode(commitment)
|
||||
|
||||
val request =
|
||||
Request
|
||||
.Builder()
|
||||
.header("Accept", "application/vnd.opentimestamps.v1")
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.url(url)
|
||||
.get()
|
||||
.build()
|
||||
|
||||
val client = okHttpClient(url)
|
||||
client.newCall(request).executeAsync().use { response ->
|
||||
withContext(Dispatchers.IO) {
|
||||
if (response.isSuccessful) {
|
||||
val ctx = StreamDeserializationContext(response.body.bytes())
|
||||
Timestamp.deserialize(ctx, commitment)
|
||||
} else {
|
||||
throw CommitmentNotFoundException("Calendar response a status code != 200: " + response.code)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: DeserializationException) {
|
||||
throw e
|
||||
} catch (e: ExceededSizeException) {
|
||||
throw e
|
||||
} catch (e: CommitmentNotFoundException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
throw UrlException(e.message)
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* 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.nip03Timestamp.okhttp
|
||||
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsResolver
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsResolverBuilder
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.OtsBlockHeightCache
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
class OkHttpOtsResolverBuilder(
|
||||
val cache: OtsBlockHeightCache = OtsBlockHeightCache(),
|
||||
val okHttpClient: (url: String) -> OkHttpClient,
|
||||
) : OtsResolverBuilder {
|
||||
override fun build(): OtsResolver =
|
||||
OtsResolver(
|
||||
explorer =
|
||||
OkHttpBitcoinExplorer(
|
||||
baseUrl = {
|
||||
OkHttpBitcoinExplorer.BLOCKSTREAM_API_URL
|
||||
},
|
||||
client = okHttpClient(OkHttpBitcoinExplorer.MEMPOOL_API_URL),
|
||||
cache = cache,
|
||||
),
|
||||
calendar = OkHttpCalendar(okHttpClient),
|
||||
)
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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.nip46RemoteSigner.jackson
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser
|
||||
import com.fasterxml.jackson.databind.DeserializationContext
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerMessage
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequest
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponse
|
||||
import kotlin.jvm.java
|
||||
|
||||
class BunkerMessageDeserializer : StdDeserializer<BunkerMessage>(BunkerMessage::class.java) {
|
||||
override fun deserialize(
|
||||
jp: JsonParser,
|
||||
ctxt: DeserializationContext,
|
||||
): BunkerMessage {
|
||||
val jsonObject: JsonNode = jp.codec.readTree(jp)
|
||||
val isRequest = jsonObject.has("method")
|
||||
|
||||
if (isRequest) {
|
||||
return jp.codec.treeToValue(jsonObject, BunkerRequest::class.java)
|
||||
} else {
|
||||
return jp.codec.treeToValue(jsonObject, BunkerResponse::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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.nip46RemoteSigner.jackson
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser
|
||||
import com.fasterxml.jackson.databind.DeserializationContext
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.toTypedArray
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequest
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestConnect
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestGetPublicKey
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestGetRelays
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestNip04Decrypt
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestNip04Encrypt
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestNip44Decrypt
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestNip44Encrypt
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestPing
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestSign
|
||||
|
||||
class BunkerRequestDeserializer : StdDeserializer<BunkerRequest>(BunkerRequest::class.java) {
|
||||
override fun deserialize(
|
||||
jp: JsonParser,
|
||||
ctxt: DeserializationContext,
|
||||
): BunkerRequest {
|
||||
val jsonObject: JsonNode = jp.codec.readTree(jp)
|
||||
val id = jsonObject.get("id").asText().intern()
|
||||
val method = jsonObject.get("method").asText().intern()
|
||||
val params = jsonObject.get("params")?.toTypedArray { it.asText().intern() } ?: emptyArray()
|
||||
|
||||
return when (method) {
|
||||
BunkerRequestConnect.METHOD_NAME -> BunkerRequestConnect.parse(id, params)
|
||||
BunkerRequestGetPublicKey.METHOD_NAME -> BunkerRequestGetPublicKey.parse(id, params)
|
||||
BunkerRequestGetRelays.METHOD_NAME -> BunkerRequestGetRelays.parse(id, params)
|
||||
|
||||
BunkerRequestNip04Decrypt.METHOD_NAME -> BunkerRequestNip04Decrypt.parse(id, params)
|
||||
BunkerRequestNip04Encrypt.METHOD_NAME -> BunkerRequestNip04Encrypt.parse(id, params)
|
||||
BunkerRequestNip44Decrypt.METHOD_NAME -> BunkerRequestNip44Decrypt.parse(id, params)
|
||||
BunkerRequestNip44Encrypt.METHOD_NAME -> BunkerRequestNip44Encrypt.parse(id, params)
|
||||
|
||||
BunkerRequestPing.METHOD_NAME -> BunkerRequestPing.parse(id, params)
|
||||
BunkerRequestSign.METHOD_NAME -> BunkerRequestSign.parse(id, params)
|
||||
else -> BunkerRequest(id, method, params)
|
||||
}
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 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.nip46RemoteSigner.jackson
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator
|
||||
import com.fasterxml.jackson.databind.SerializerProvider
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequest
|
||||
import kotlin.collections.forEach
|
||||
|
||||
class BunkerRequestSerializer : StdSerializer<BunkerRequest>(BunkerRequest::class.java) {
|
||||
override fun serialize(
|
||||
value: BunkerRequest,
|
||||
gen: JsonGenerator,
|
||||
provider: SerializerProvider,
|
||||
) {
|
||||
gen.writeStartObject()
|
||||
gen.writeStringField("id", value.id)
|
||||
gen.writeStringField("method", value.method)
|
||||
gen.writeArrayFieldStart("params")
|
||||
value.params.forEach {
|
||||
gen.writeString(it)
|
||||
}
|
||||
gen.writeEndArray()
|
||||
gen.writeEndObject()
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* 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.nip46RemoteSigner.jackson
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser
|
||||
import com.fasterxml.jackson.databind.DeserializationContext
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponse
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponseAck
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponseGetRelays
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponsePong
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponsePublicKey
|
||||
import com.vitorpamplona.quartz.utils.Hex
|
||||
|
||||
class BunkerResponseDeserializer : StdDeserializer<BunkerResponse>(BunkerResponse::class.java) {
|
||||
override fun deserialize(
|
||||
jp: JsonParser,
|
||||
ctxt: DeserializationContext,
|
||||
): BunkerResponse {
|
||||
val jsonObject: JsonNode = jp.codec.readTree(jp)
|
||||
|
||||
val id = jsonObject.get("id").asText().intern()
|
||||
val result = jsonObject.get("result")?.asText()
|
||||
val error = jsonObject.get("error")?.asText()
|
||||
|
||||
if (error != null) {
|
||||
return com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponseError
|
||||
.parse(id, result, error)
|
||||
}
|
||||
|
||||
if (result != null) {
|
||||
when (result) {
|
||||
BunkerResponseAck.RESULT -> return BunkerResponseAck.parse(id, result, error)
|
||||
BunkerResponsePong.RESULT -> return BunkerResponsePong.parse(id, result, error)
|
||||
else -> {
|
||||
if (result.length == 64 && Hex.isHex(result)) {
|
||||
return BunkerResponsePublicKey.parse(id, result)
|
||||
}
|
||||
|
||||
if (result.get(0) == '{') {
|
||||
try {
|
||||
return com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponseEvent
|
||||
.parse(id, result)
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
|
||||
try {
|
||||
return BunkerResponseGetRelays.parse(id, result)
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
}
|
||||
|
||||
return BunkerResponse(id, result, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return BunkerResponse(id, result, error)
|
||||
}
|
||||
}
|
||||
+40
@@ -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.nip46RemoteSigner.jackson
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator
|
||||
import com.fasterxml.jackson.databind.SerializerProvider
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponse
|
||||
|
||||
class BunkerResponseSerializer : StdSerializer<BunkerResponse>(BunkerResponse::class.java) {
|
||||
override fun serialize(
|
||||
value: BunkerResponse,
|
||||
gen: JsonGenerator,
|
||||
provider: SerializerProvider,
|
||||
) {
|
||||
gen.writeStartObject()
|
||||
gen.writeStringField("id", value.id)
|
||||
value.result?.let { gen.writeStringField("result", value.result) }
|
||||
value.error?.let { gen.writeStringField("error", it) }
|
||||
gen.writeEndObject()
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 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.nip47WalletConnect.jackson
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser
|
||||
import com.fasterxml.jackson.databind.DeserializationContext
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.PayInvoiceMethod
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.Request
|
||||
|
||||
class RequestDeserializer : StdDeserializer<Request>(Request::class.java) {
|
||||
override fun deserialize(
|
||||
jp: JsonParser,
|
||||
ctxt: DeserializationContext,
|
||||
): Request? {
|
||||
val jsonObject: JsonNode = jp.codec.readTree(jp)
|
||||
val method = jsonObject.get("method")?.asText()
|
||||
|
||||
if (method == "pay_invoice") {
|
||||
return jp.codec.treeToValue(jsonObject, PayInvoiceMethod::class.java)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 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.nip47WalletConnect.jackson
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser
|
||||
import com.fasterxml.jackson.databind.DeserializationContext
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.PayInvoiceErrorResponse
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.PayInvoiceSuccessResponse
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.Response
|
||||
|
||||
class ResponseDeserializer : StdDeserializer<Response>(Response::class.java) {
|
||||
override fun deserialize(
|
||||
jp: JsonParser,
|
||||
ctxt: DeserializationContext,
|
||||
): Response? {
|
||||
val jsonObject: JsonNode = jp.codec.readTree(jp)
|
||||
val resultType = jsonObject.get("result_type")?.asText()
|
||||
|
||||
if (resultType == "pay_invoice") {
|
||||
val result = jsonObject.get("result")
|
||||
val error = jsonObject.get("error")
|
||||
if (result != null) {
|
||||
return jp.codec.treeToValue(jsonObject, PayInvoiceSuccessResponse::class.java)
|
||||
}
|
||||
if (error != null) {
|
||||
return jp.codec.treeToValue(jsonObject, PayInvoiceErrorResponse::class.java)
|
||||
}
|
||||
} else {
|
||||
// tries to guess
|
||||
if (jsonObject.get("result")?.get("preimage") != null) {
|
||||
return jp.codec.treeToValue(jsonObject, PayInvoiceSuccessResponse::class.java)
|
||||
}
|
||||
if (jsonObject.get("error")?.get("code") != null) {
|
||||
return jp.codec.treeToValue(jsonObject, PayInvoiceErrorResponse::class.java)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 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.nip59Giftwrap.rumors.jackson
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser
|
||||
import com.fasterxml.jackson.databind.DeserializationContext
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.toTypedArray
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.rumors.Rumor
|
||||
|
||||
class RumorDeserializer : StdDeserializer<Rumor>(Rumor::class.java) {
|
||||
override fun deserialize(
|
||||
jp: JsonParser,
|
||||
ctxt: DeserializationContext,
|
||||
): Rumor {
|
||||
val jsonObject: JsonNode = jp.codec.readTree(jp)
|
||||
return Rumor(
|
||||
id = jsonObject.get("id")?.asText()?.intern(),
|
||||
pubKey = jsonObject.get("pubkey")?.asText()?.intern(),
|
||||
createdAt = jsonObject.get("created_at")?.asLong(),
|
||||
kind = jsonObject.get("kind")?.asInt(),
|
||||
tags =
|
||||
jsonObject.get("tags").toTypedArray {
|
||||
it.toTypedArray { s -> if (s.isNull) "" else s.asText().intern() }
|
||||
},
|
||||
content = jsonObject.get("content")?.asText(),
|
||||
)
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* 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.nip59Giftwrap.rumors.jackson
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator
|
||||
import com.fasterxml.jackson.databind.SerializerProvider
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.rumors.Rumor
|
||||
|
||||
class RumorSerializer : StdSerializer<Rumor>(Rumor::class.java) {
|
||||
override fun serialize(
|
||||
event: Rumor,
|
||||
gen: JsonGenerator,
|
||||
provider: SerializerProvider,
|
||||
) {
|
||||
gen.writeStartObject()
|
||||
event.id?.let { gen.writeStringField("id", it) }
|
||||
event.pubKey?.let { gen.writeStringField("pubkey", it) }
|
||||
event.createdAt?.let { gen.writeNumberField("created_at", it) }
|
||||
event.kind?.let { gen.writeNumberField("kind", it) }
|
||||
event.tags?.let {
|
||||
gen.writeArrayFieldStart("tags")
|
||||
event.tags.forEach { tag -> gen.writeArray(tag, 0, tag.size) }
|
||||
gen.writeEndArray()
|
||||
}
|
||||
event.content?.let { gen.writeStringField("content", it) }
|
||||
gen.writeEndObject()
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 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
|
||||
|
||||
import java.net.URI
|
||||
import java.net.URL
|
||||
|
||||
actual fun makeAbsoluteIfRelativeUrl(
|
||||
baseUrl: String,
|
||||
potentiallyRelativeUrl: String,
|
||||
): String =
|
||||
try {
|
||||
val apiUrl = URI(potentiallyRelativeUrl)
|
||||
if (apiUrl.isAbsolute) {
|
||||
potentiallyRelativeUrl
|
||||
} else {
|
||||
URL(URL(baseUrl), potentiallyRelativeUrl).toString()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
potentiallyRelativeUrl
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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 java.math.BigDecimal as JBigDecimal
|
||||
|
||||
actual typealias BigDecimal = JBigDecimal
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 java.util.BitSet as JvmBitSet
|
||||
|
||||
actual typealias BitSet = JvmBitSet
|
||||
|
||||
actual fun bitSetValueOf(bytes: ByteArray): BitSet = JvmBitSet.valueOf(bytes)
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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 java.io.ByteArrayOutputStream
|
||||
import java.util.zip.GZIPInputStream
|
||||
import java.util.zip.GZIPOutputStream
|
||||
|
||||
actual object GZip {
|
||||
actual fun compress(content: String): ByteArray {
|
||||
val bos = ByteArrayOutputStream()
|
||||
GZIPOutputStream(bos).bufferedWriter(Charsets.UTF_8).use { it.write(content) }
|
||||
return bos.toByteArray()
|
||||
}
|
||||
|
||||
actual fun decompress(content: ByteArray): String = GZIPInputStream(content.inputStream()).bufferedReader(Charsets.UTF_8).use { it.readText() }
|
||||
}
|
||||
@@ -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
|
||||
|
||||
import org.czeal.rfc3986.URIReference
|
||||
|
||||
actual object Rfc3986 {
|
||||
actual fun normalize(uri: String) =
|
||||
URIReference
|
||||
.parse(uri)
|
||||
.normalize()
|
||||
.toString()
|
||||
|
||||
actual fun isValidUrl(url: String): Boolean =
|
||||
runCatching {
|
||||
URIReference.parse(url)
|
||||
}.isSuccess
|
||||
|
||||
actual fun normalizeAndRemoveFragment(url: String): String =
|
||||
URIReference
|
||||
.parse(url)
|
||||
.normalize()
|
||||
.toStringNoFragment()
|
||||
.intern()
|
||||
|
||||
actual fun host(url: String): String = URIReference.parse(url).host.value
|
||||
}
|
||||
|
||||
fun URIReference.toStringSchemeHost(): String {
|
||||
val sb = StringBuilder()
|
||||
|
||||
if (scheme != null) sb.append(scheme).append(":")
|
||||
if (authority != null) sb.append("//").append(authority.toString())
|
||||
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
fun URIReference.toStringNoFragment(): String {
|
||||
val sb = StringBuilder()
|
||||
|
||||
if (scheme != null) sb.append(scheme).append(":")
|
||||
if (authority != null) sb.append("//").append(authority.toString())
|
||||
if (path != null) sb.append(path)
|
||||
if (query != null) sb.append("?").append(query)
|
||||
|
||||
return sb.toString()
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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 java.security.SecureRandom as JvmSecureRandom
|
||||
|
||||
actual typealias SecureRandom = JvmSecureRandom
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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 fun String.internIfPossible() = this.intern()
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 java.text.Normalizer
|
||||
|
||||
actual class UnicodeNormalizer {
|
||||
actual fun normalizeNFKC(input: String): String = Normalizer.normalize(input, Normalizer.Form.NFKC)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
import java.net.URLDecoder
|
||||
import java.net.URLEncoder
|
||||
|
||||
actual object UrlEncoder {
|
||||
actual fun encode(value: String): String = URLEncoder.encode(value, "utf-8")
|
||||
|
||||
actual fun decode(value: String): String = URLDecoder.decode(value, "utf-8")
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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 com.linkedin.urls.detection.UrlDetector
|
||||
import com.linkedin.urls.detection.UrlDetectorOptions
|
||||
|
||||
actual fun fastFindURLs(text: String): List<String> = UrlDetector(text, UrlDetectorOptions.Default).detect().map { it.originalUrl }
|
||||
+323
@@ -0,0 +1,323 @@
|
||||
/**
|
||||
* 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 com.vitorpamplona.quartz.utils.cache.CacheCollectors.BiFilter
|
||||
import com.vitorpamplona.quartz.utils.cache.CacheCollectors.BiMapper
|
||||
import com.vitorpamplona.quartz.utils.cache.CacheCollectors.BiMapperNotNull
|
||||
import com.vitorpamplona.quartz.utils.cache.CacheCollectors.BiNotNullMapper
|
||||
import com.vitorpamplona.quartz.utils.cache.CacheCollectors.BiSumOfLong
|
||||
import java.util.function.BiConsumer
|
||||
|
||||
class BiFilterCollector<K, V>(
|
||||
private val filter: BiFilter<K, V>,
|
||||
) : BiConsumer<K, V> {
|
||||
val results: ArrayList<V> = ArrayList()
|
||||
|
||||
override fun accept(
|
||||
k: K,
|
||||
v: V,
|
||||
) {
|
||||
if (filter.filter(k, v)) {
|
||||
results.add(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BiFilterUniqueCollector<K, V>(
|
||||
private val filter: BiFilter<K, V>,
|
||||
) : BiConsumer<K, V> {
|
||||
val results: HashSet<V> = HashSet()
|
||||
|
||||
override fun accept(
|
||||
k: K,
|
||||
v: V,
|
||||
) {
|
||||
if (filter.filter(k, v)) {
|
||||
results.add(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BiMapCollector<K, V, R>(
|
||||
private val mapper: BiMapper<K, V, R?>,
|
||||
) : BiConsumer<K, V> {
|
||||
val results: ArrayList<R> = ArrayList()
|
||||
|
||||
override fun accept(
|
||||
k: K,
|
||||
v: V,
|
||||
) {
|
||||
val result = mapper.map(k, v)
|
||||
if (result != null) {
|
||||
results.add(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BiMapUniqueCollector<K, V, R>(
|
||||
private val mapper: BiMapper<K, V, R?>,
|
||||
) : BiConsumer<K, V> {
|
||||
val results: HashSet<R> = HashSet()
|
||||
|
||||
override fun accept(
|
||||
k: K,
|
||||
v: V,
|
||||
) {
|
||||
val result = mapper.map(k, v)
|
||||
if (result != null) {
|
||||
results.add(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BiMapFlattenCollector<K, V, R>(
|
||||
private val mapper: BiMapper<K, V, Collection<R>?>,
|
||||
) : BiConsumer<K, V> {
|
||||
val results: ArrayList<R> = ArrayList()
|
||||
|
||||
override fun accept(
|
||||
k: K,
|
||||
v: V,
|
||||
) {
|
||||
val result = mapper.map(k, v)
|
||||
if (result != null) {
|
||||
results.addAll(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BiMapFlattenUniqueCollector<K, V, R>(
|
||||
private val mapper: BiMapper<K, V, Collection<R>?>,
|
||||
) : BiConsumer<K, V> {
|
||||
val results: HashSet<R> = HashSet()
|
||||
|
||||
override fun accept(
|
||||
k: K,
|
||||
v: V,
|
||||
) {
|
||||
val result = mapper.map(k, v)
|
||||
if (result != null) {
|
||||
results.addAll(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BiNotNullMapCollector<K, V, R>(
|
||||
private val mapper: BiNotNullMapper<K, V, R>,
|
||||
) : BiConsumer<K, V> {
|
||||
val results: ArrayList<R> = ArrayList()
|
||||
|
||||
override fun accept(
|
||||
k: K,
|
||||
v: V,
|
||||
) {
|
||||
results.add(mapper.map(k, v))
|
||||
}
|
||||
}
|
||||
|
||||
class BiMaxOfCollector<K, V>(
|
||||
private val filter: BiFilter<K, V>,
|
||||
private val comparator: Comparator<V>,
|
||||
) : BiConsumer<K, V> {
|
||||
private var _maxK: K? = null
|
||||
private var _maxV: V? = null
|
||||
|
||||
val maxK: K? get() = _maxK
|
||||
val maxV: V? get() = _maxV
|
||||
|
||||
override fun accept(
|
||||
k: K,
|
||||
v: V,
|
||||
) {
|
||||
if (filter.filter(k, v)) {
|
||||
if (_maxK == null || comparator.compare(v, _maxV) > 0) {
|
||||
_maxK = k
|
||||
_maxV = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BiSumOfCollector<K, V>(
|
||||
private val mapper: CacheCollectors.BiSumOf<K, V>,
|
||||
) : BiConsumer<K, V> {
|
||||
private var _sum = 0
|
||||
val sum: Int get() = _sum
|
||||
|
||||
override fun accept(
|
||||
k: K,
|
||||
v: V,
|
||||
) {
|
||||
_sum += mapper.map(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
class BiSumOfLongCollector<K, V>(
|
||||
private val mapper: BiSumOfLong<K, V>,
|
||||
) : BiConsumer<K, V> {
|
||||
private var _sum = 0L
|
||||
val sum: Long get() = _sum
|
||||
|
||||
override fun accept(
|
||||
k: K,
|
||||
v: V,
|
||||
) {
|
||||
_sum += mapper.map(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
class BiGroupByCollector<K, V, R>(
|
||||
private val mapper: BiNotNullMapper<K, V, R>,
|
||||
) : BiConsumer<K, V> {
|
||||
val results = HashMap<R, ArrayList<V>>()
|
||||
|
||||
override fun accept(
|
||||
k: K,
|
||||
v: V,
|
||||
) {
|
||||
val group = mapper.map(k, v)
|
||||
val list = results[group]
|
||||
if (list == null) {
|
||||
val answer = ArrayList<V>()
|
||||
answer.add(v)
|
||||
results[group] = answer
|
||||
} else {
|
||||
list.add(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BiCountByGroupCollector<K, V, R>(
|
||||
private val mapper: BiNotNullMapper<K, V, R>,
|
||||
) : BiConsumer<K, V> {
|
||||
val results = HashMap<R, Int>()
|
||||
|
||||
override fun accept(
|
||||
k: K,
|
||||
v: V,
|
||||
) {
|
||||
val group = mapper.map(k, v)
|
||||
val count = results[group]
|
||||
if (count == null) {
|
||||
results[group] = 1
|
||||
} else {
|
||||
results[group] = count + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BiSumByGroupCollector<K, V, R>(
|
||||
private val mapper: BiNotNullMapper<K, V, R>,
|
||||
private val sumOf: BiNotNullMapper<K, V, Long>,
|
||||
) : BiConsumer<K, V> {
|
||||
val results = HashMap<R, Long>()
|
||||
|
||||
override fun accept(
|
||||
k: K,
|
||||
v: V,
|
||||
) {
|
||||
val group = mapper.map(k, v)
|
||||
val sum = results[group]
|
||||
if (sum == null) {
|
||||
results[group] = sumOf.map(k, v)
|
||||
} else {
|
||||
results[group] = sum + sumOf.map(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BiCountIfCollector<K, V>(
|
||||
private val filter: BiFilter<K, V>,
|
||||
) : BiConsumer<K, V> {
|
||||
private var _count = 0
|
||||
val count: Int get() = _count
|
||||
|
||||
override fun accept(
|
||||
k: K,
|
||||
v: V,
|
||||
) {
|
||||
if (filter.filter(k, v)) _count++
|
||||
}
|
||||
}
|
||||
|
||||
class BiAssociateCollector<K, V, T, U>(
|
||||
size: Int,
|
||||
private val mapper: BiMapperNotNull<K, V, Pair<T, U>>,
|
||||
) : BiConsumer<K, V> {
|
||||
val results: LinkedHashMap<T, U> = LinkedHashMap(size)
|
||||
|
||||
override fun accept(
|
||||
k: K,
|
||||
v: V,
|
||||
) {
|
||||
val pair = mapper.map(k, v)
|
||||
results[pair.first] = pair.second
|
||||
}
|
||||
}
|
||||
|
||||
class BiAssociateNotNullCollector<K, V, T, U>(
|
||||
size: Int,
|
||||
private val mapper: BiMapper<K, V, Pair<T, U>?>,
|
||||
) : BiConsumer<K, V> {
|
||||
val results: LinkedHashMap<T, U> = LinkedHashMap(size)
|
||||
|
||||
override fun accept(
|
||||
k: K,
|
||||
v: V,
|
||||
) {
|
||||
val pair = mapper.map(k, v)
|
||||
if (pair != null) {
|
||||
results[pair.first] = pair.second
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BiAssociateWithCollector<K, V, U>(
|
||||
size: Int,
|
||||
private val mapper: BiMapper<K, V, U?>,
|
||||
) : BiConsumer<K, V> {
|
||||
val results: LinkedHashMap<K, U?> = LinkedHashMap(size)
|
||||
|
||||
override fun accept(
|
||||
k: K,
|
||||
v: V,
|
||||
) {
|
||||
results[k] = mapper.map(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
class BiAssociateNotNullWithCollector<K, V, U>(
|
||||
size: Int,
|
||||
private val mapper: BiMapper<K, V, U>,
|
||||
) : BiConsumer<K, V> {
|
||||
val results: LinkedHashMap<K, U> = LinkedHashMap(size)
|
||||
|
||||
override fun accept(
|
||||
k: K,
|
||||
v: V,
|
||||
) {
|
||||
val newValue = mapper.map(k, v)
|
||||
if (newValue != null) {
|
||||
results[k] = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
/**
|
||||
* 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 java.util.function.BiConsumer
|
||||
|
||||
class MyBiConsumer<K, V>(
|
||||
private val consumer: ICacheBiConsumer<K, V>,
|
||||
) : BiConsumer<K, V> {
|
||||
override fun accept(
|
||||
k: K,
|
||||
v: V,
|
||||
) {
|
||||
consumer.accept(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
interface CacheOperations<K, V> : ICacheOperations<K, V> {
|
||||
fun forEach(consumer: BiConsumer<K, V>)
|
||||
|
||||
override fun size(): Int
|
||||
|
||||
override fun forEach(consumer: ICacheBiConsumer<K, V>) {
|
||||
forEach(MyBiConsumer(consumer))
|
||||
}
|
||||
|
||||
override fun filter(consumer: CacheCollectors.BiFilter<K, V>): List<V> {
|
||||
val runner = BiFilterCollector(consumer)
|
||||
forEach(runner)
|
||||
return runner.results
|
||||
}
|
||||
|
||||
override fun filterIntoSet(consumer: CacheCollectors.BiFilter<K, V>): Set<V> {
|
||||
val runner = BiFilterUniqueCollector(consumer)
|
||||
forEach(runner)
|
||||
return runner.results
|
||||
}
|
||||
|
||||
override fun <R> map(consumer: CacheCollectors.BiNotNullMapper<K, V, R>): List<R> {
|
||||
val runner = BiNotNullMapCollector(consumer)
|
||||
forEach(runner)
|
||||
return runner.results
|
||||
}
|
||||
|
||||
override fun <R> mapNotNull(consumer: CacheCollectors.BiMapper<K, V, R?>): List<R> {
|
||||
val runner = BiMapCollector(consumer)
|
||||
forEach(runner)
|
||||
return runner.results
|
||||
}
|
||||
|
||||
override fun <R> mapNotNullIntoSet(consumer: CacheCollectors.BiMapper<K, V, R?>): Set<R> {
|
||||
val runner = BiMapUniqueCollector(consumer)
|
||||
forEach(runner)
|
||||
return runner.results
|
||||
}
|
||||
|
||||
override fun <R> mapFlatten(consumer: CacheCollectors.BiMapper<K, V, Collection<R>?>): List<R> {
|
||||
val runner = BiMapFlattenCollector(consumer)
|
||||
forEach(runner)
|
||||
return runner.results
|
||||
}
|
||||
|
||||
override fun <R> mapFlattenIntoSet(consumer: CacheCollectors.BiMapper<K, V, Collection<R>?>): Set<R> {
|
||||
val runner = BiMapFlattenUniqueCollector(consumer)
|
||||
forEach(runner)
|
||||
return runner.results
|
||||
}
|
||||
|
||||
override fun maxOrNullOf(
|
||||
filter: CacheCollectors.BiFilter<K, V>,
|
||||
comparator: Comparator<V>,
|
||||
): V? {
|
||||
val runner = BiMaxOfCollector(filter, comparator)
|
||||
forEach(runner)
|
||||
return runner.maxV
|
||||
}
|
||||
|
||||
override fun sumOf(consumer: CacheCollectors.BiSumOf<K, V>): Int {
|
||||
val runner = BiSumOfCollector(consumer)
|
||||
forEach(runner)
|
||||
return runner.sum
|
||||
}
|
||||
|
||||
override fun sumOfLong(consumer: CacheCollectors.BiSumOfLong<K, V>): Long {
|
||||
val runner = BiSumOfLongCollector(consumer)
|
||||
forEach(runner)
|
||||
return runner.sum
|
||||
}
|
||||
|
||||
override fun <R> groupBy(consumer: CacheCollectors.BiNotNullMapper<K, V, R>): Map<R, List<V>> {
|
||||
val runner = BiGroupByCollector(consumer)
|
||||
forEach(runner)
|
||||
return runner.results
|
||||
}
|
||||
|
||||
override fun <R> countByGroup(consumer: CacheCollectors.BiNotNullMapper<K, V, R>): Map<R, Int> {
|
||||
val runner = BiCountByGroupCollector(consumer)
|
||||
forEach(runner)
|
||||
return runner.results
|
||||
}
|
||||
|
||||
override fun <R> sumByGroup(
|
||||
groupMap: CacheCollectors.BiNotNullMapper<K, V, R>,
|
||||
sumOf: CacheCollectors.BiNotNullMapper<K, V, Long>,
|
||||
): Map<R, Long> {
|
||||
val runner = BiSumByGroupCollector(groupMap, sumOf)
|
||||
forEach(runner)
|
||||
return runner.results
|
||||
}
|
||||
|
||||
override fun count(consumer: CacheCollectors.BiFilter<K, V>): Int {
|
||||
val runner = BiCountIfCollector(consumer)
|
||||
forEach(runner)
|
||||
return runner.count
|
||||
}
|
||||
|
||||
override fun <T, U> associate(transform: (K, V) -> Pair<T, U>): Map<T, U> {
|
||||
val runner = BiAssociateCollector(size(), transform)
|
||||
forEach(runner)
|
||||
return runner.results
|
||||
}
|
||||
|
||||
override fun <U> associateWith(transform: (K, V) -> U?): Map<K, U?> {
|
||||
val runner = BiAssociateWithCollector(size(), transform)
|
||||
forEach(runner)
|
||||
return runner.results
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* 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 java.util.concurrent.ConcurrentSkipListMap
|
||||
import java.util.function.BiConsumer
|
||||
|
||||
actual class LargeCache<K, V> : CacheOperations<K, V> {
|
||||
private val cache = ConcurrentSkipListMap<K, V>()
|
||||
|
||||
actual fun keys(): Set<K> = cache.keys
|
||||
|
||||
actual fun values(): Iterable<V> = cache.values
|
||||
|
||||
actual fun get(key: K) = cache.get(key)
|
||||
|
||||
actual fun remove(key: K) = cache.remove(key)
|
||||
|
||||
actual override fun size() = cache.size
|
||||
|
||||
actual fun isEmpty() = cache.isEmpty()
|
||||
|
||||
actual fun clear() = cache.clear()
|
||||
|
||||
actual fun containsKey(key: K) = cache.containsKey(key)
|
||||
|
||||
actual fun put(
|
||||
key: K,
|
||||
value: V,
|
||||
) {
|
||||
cache.put(key, value)
|
||||
}
|
||||
|
||||
actual fun getOrCreate(
|
||||
key: K,
|
||||
builder: (key: K) -> V,
|
||||
): V {
|
||||
val value = cache.get(key)
|
||||
|
||||
return if (value != null) {
|
||||
value
|
||||
} else {
|
||||
val newObject = builder(key)
|
||||
cache.putIfAbsent(key, newObject) ?: newObject
|
||||
}
|
||||
}
|
||||
|
||||
actual fun createIfAbsent(
|
||||
key: K,
|
||||
builder: (key: K) -> V,
|
||||
): Boolean {
|
||||
val value = cache.get(key)
|
||||
return if (value != null) {
|
||||
false
|
||||
} else {
|
||||
val newObject = builder(key)
|
||||
cache.putIfAbsent(key, newObject) == null
|
||||
}
|
||||
}
|
||||
|
||||
override fun forEach(consumer: BiConsumer<K, V>) {
|
||||
cache.forEach(consumer)
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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 java.security.GeneralSecurityException
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
actual class AESCBC actual constructor(
|
||||
actual val keyBytes: ByteArray,
|
||||
actual val iv: ByteArray,
|
||||
) : NostrCipher {
|
||||
private fun newCipher() = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
||||
|
||||
private fun keySpec() = SecretKeySpec(keyBytes, "AES")
|
||||
|
||||
private fun param() = IvParameterSpec(iv)
|
||||
|
||||
actual override fun name() = NAME
|
||||
|
||||
actual override fun encrypt(bytesToEncrypt: ByteArray): ByteArray =
|
||||
with(newCipher()) {
|
||||
init(Cipher.ENCRYPT_MODE, keySpec(), param())
|
||||
doFinal(bytesToEncrypt)
|
||||
}
|
||||
|
||||
actual override fun decrypt(bytesToDecrypt: ByteArray): ByteArray =
|
||||
with(newCipher()) {
|
||||
init(Cipher.DECRYPT_MODE, keySpec(), param())
|
||||
doFinal(bytesToDecrypt)
|
||||
}
|
||||
|
||||
actual override fun decryptOrNull(bytesToDecrypt: ByteArray): ByteArray? =
|
||||
try {
|
||||
decrypt(bytesToDecrypt)
|
||||
} catch (e: GeneralSecurityException) {
|
||||
Log.w("AESCBC", "Failed to decrypt", e)
|
||||
null
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val NAME = "aes-cbc"
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* 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.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import java.security.GeneralSecurityException
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.GCMParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
actual class AESGCM actual constructor(
|
||||
actual val keyBytes: ByteArray,
|
||||
actual val nonce: ByteArray,
|
||||
) : NostrCipher {
|
||||
private fun newCipher() = Cipher.getInstance("AES/GCM/NoPadding")
|
||||
|
||||
private fun keySpec() = SecretKeySpec(keyBytes, "AES")
|
||||
|
||||
private fun param() = GCMParameterSpec(128, nonce)
|
||||
|
||||
actual override fun name() = NAME
|
||||
|
||||
fun copyUsingUTF8Nonce(): AESGCM =
|
||||
AESGCM(
|
||||
keyBytes,
|
||||
nonce.toHexKey().toByteArray(Charsets.UTF_8),
|
||||
)
|
||||
|
||||
actual override fun encrypt(bytesToEncrypt: ByteArray): ByteArray =
|
||||
with(newCipher()) {
|
||||
init(Cipher.ENCRYPT_MODE, keySpec(), param())
|
||||
doFinal(bytesToEncrypt)
|
||||
}
|
||||
|
||||
actual override fun decrypt(bytesToDecrypt: ByteArray): ByteArray =
|
||||
with(newCipher()) {
|
||||
init(Cipher.DECRYPT_MODE, keySpec(), param())
|
||||
doFinal(bytesToDecrypt)
|
||||
}
|
||||
|
||||
actual override fun decryptOrNull(bytesToDecrypt: ByteArray): ByteArray? =
|
||||
try {
|
||||
decrypt(bytesToDecrypt)
|
||||
} catch (e: GeneralSecurityException) {
|
||||
Log.w("AESGCM", "Failed to decrypt", e)
|
||||
null
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val NAME = "aes-gcm"
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 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 java.security.MessageDigest
|
||||
|
||||
actual class DigestInstance actual constructor(
|
||||
algorithm: String,
|
||||
) {
|
||||
val digest: MessageDigest = MessageDigest.getInstance(algorithm)
|
||||
|
||||
actual fun update(array: ByteArray) = digest.update(array)
|
||||
|
||||
actual fun update(byte: Byte) = digest.update(byte)
|
||||
|
||||
actual fun digest(): ByteArray = digest.digest()
|
||||
|
||||
actual fun digest(input: ByteArray): ByteArray = digest.digest(input)
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* 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 javax.crypto.SecretKey
|
||||
|
||||
/**
|
||||
* Simple key spec that doesn't clone the key bytearray
|
||||
*/
|
||||
class FixedKey(
|
||||
val key: ByteArray,
|
||||
val algo: String,
|
||||
) : SecretKey {
|
||||
override fun getAlgorithm() = algo
|
||||
|
||||
override fun getEncoded() = key
|
||||
|
||||
override fun getFormat() = "RAW"
|
||||
|
||||
override fun hashCode() = key.contentHashCode() xor algo.hashCode()
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is FixedKey) return false
|
||||
|
||||
if (!key.contentEquals(other.key)) return false
|
||||
|
||||
// Old algorithm names
|
||||
val thatAlg = other.algorithm
|
||||
if (!(thatAlg.equals(this.algorithm, ignoreCase = true))) {
|
||||
if ((
|
||||
!(thatAlg.equals("DESede", ignoreCase = true)) ||
|
||||
!(this.algorithm.equals("TripleDES", ignoreCase = true))
|
||||
) &&
|
||||
(
|
||||
!(thatAlg.equals("TripleDES", ignoreCase = true)) ||
|
||||
!(this.algorithm.equals("DESede", ignoreCase = true))
|
||||
)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun destroy() = key.fill(0)
|
||||
|
||||
override fun isDestroyed() = key.all { it.toInt() == 0 }
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 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 javax.crypto.Mac
|
||||
|
||||
actual class MacInstance actual constructor(
|
||||
algorithm: String,
|
||||
key: ByteArray,
|
||||
) {
|
||||
val mac: Mac =
|
||||
Mac.getInstance(algorithm).apply {
|
||||
init(FixedKey(key, algorithm))
|
||||
}
|
||||
|
||||
actual fun init(
|
||||
key: ByteArray,
|
||||
algorithm: String,
|
||||
) = mac.init(FixedKey(key, algorithm))
|
||||
|
||||
actual fun getMacLength() = mac.macLength
|
||||
|
||||
actual fun update(array: ByteArray) = mac.update(array)
|
||||
|
||||
actual fun update(byte: Byte) = mac.update(byte)
|
||||
|
||||
actual fun doFinal() = mac.doFinal()
|
||||
|
||||
actual fun doFinal(
|
||||
output: ByteArray,
|
||||
offset: Int,
|
||||
) = mac.doFinal(output, offset)
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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
|
||||
|
||||
val pool = Sha256Pool(5) // max parallel operations
|
||||
|
||||
actual fun sha256(data: ByteArray) = pool.hash(data)
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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 java.security.MessageDigest
|
||||
|
||||
class Sha256Hasher {
|
||||
val digest = MessageDigest.getInstance("SHA-256")
|
||||
|
||||
fun hash(byteArray: ByteArray) = digest.digest(byteArray).also { digest.reset() }
|
||||
|
||||
fun digest(byteArray: ByteArray) = digest.digest(byteArray)
|
||||
|
||||
fun reset() = digest.reset()
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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 com.vitorpamplona.quartz.utils.Log
|
||||
import java.util.concurrent.ArrayBlockingQueue
|
||||
|
||||
class Sha256Pool(
|
||||
size: Int,
|
||||
) {
|
||||
private val pool = ArrayBlockingQueue<Sha256Hasher>(size)
|
||||
|
||||
init {
|
||||
repeat(size) {
|
||||
pool.add(Sha256Hasher())
|
||||
}
|
||||
}
|
||||
|
||||
private fun acquire(): Sha256Hasher {
|
||||
if (pool.isEmpty()) {
|
||||
Log.w("SHA256Pool", "Pool running low in available digests")
|
||||
}
|
||||
return pool.take()
|
||||
}
|
||||
|
||||
private fun release(digest: Sha256Hasher) {
|
||||
digest.reset()
|
||||
pool.put(digest)
|
||||
}
|
||||
|
||||
fun hash(byteArray: ByteArray): ByteArray {
|
||||
val hasher = acquire()
|
||||
try {
|
||||
return hasher.digest(byteArray)
|
||||
} finally {
|
||||
release(hasher)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user