Merge upstream changes and adjust.

This commit is contained in:
KotlinGeekDev
2025-09-12 14:14:52 +01:00
266 changed files with 3630 additions and 2117 deletions
@@ -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.relay.client
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.IRelayClientListener
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayPool
import com.vitorpamplona.quartz.nip01Core.relay.client.single.newSubId
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
interface INostrClient {
fun relayStatusFlow(): StateFlow<RelayPool.RelayPoolStatus>
fun connect()
fun disconnect()
fun reconnect(onlyIfChanged: Boolean = false)
fun isActive(): Boolean
fun openReqSubscription(
subId: String = newSubId(),
filters: Map<NormalizedRelayUrl, List<Filter>>,
)
fun openCountSubscription(
subId: String = newSubId(),
filters: Map<NormalizedRelayUrl, List<Filter>>,
)
fun close(subscriptionId: String)
fun sendIfExists(
event: Event,
connectedRelay: NormalizedRelayUrl,
)
fun send(
event: Event,
relayList: Set<NormalizedRelayUrl>,
)
fun subscribe(listener: IRelayClientListener)
fun unsubscribe(listener: IRelayClientListener)
fun getReqFiltersOrNull(subId: String): Map<NormalizedRelayUrl, List<Filter>>?
fun getCountFiltersOrNull(subId: String): Map<NormalizedRelayUrl, List<Filter>>?
}
object EmptyNostrClient : INostrClient {
override fun relayStatusFlow() = MutableStateFlow(RelayPool.RelayPoolStatus())
override fun connect() { }
override fun disconnect() { }
override fun reconnect(onlyIfChanged: Boolean) { }
override fun isActive() = false
override fun openReqSubscription(
subId: String,
filters: Map<NormalizedRelayUrl, List<Filter>>,
) { }
override fun openCountSubscription(
subId: String,
filters: Map<NormalizedRelayUrl, List<Filter>>,
) { }
override fun close(subscriptionId: String) { }
override fun sendIfExists(
event: Event,
connectedRelay: NormalizedRelayUrl,
) { }
override fun send(
event: Event,
relayList: Set<NormalizedRelayUrl>,
) { }
override fun subscribe(listener: IRelayClientListener) {}
override fun unsubscribe(listener: IRelayClientListener) {}
override fun getReqFiltersOrNull(subId: String): Map<NormalizedRelayUrl, List<Filter>>? = null
override fun getCountFiltersOrNull(subId: String): Map<NormalizedRelayUrl, List<Filter>>? = null
}
@@ -29,7 +29,6 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.pool.PoolSubscriptionRepo
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayPool
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
import com.vitorpamplona.quartz.nip01Core.relay.client.single.basic.BasicRelayClient
import com.vitorpamplona.quartz.nip01Core.relay.client.single.newSubId
import com.vitorpamplona.quartz.nip01Core.relay.client.stats.RelayStats
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
@@ -73,7 +72,8 @@ import kotlinx.coroutines.flow.stateIn
class NostrClient(
private val websocketBuilder: WebsocketBuilder,
private val scope: CoroutineScope,
) : IRelayClientListener {
) : INostrClient,
IRelayClientListener {
private val relayPool: RelayPool = RelayPool(this, ::buildRelay)
private val activeRequests: PoolSubscriptionRepository = PoolSubscriptionRepository()
private val activeCounts: PoolSubscriptionRepository = PoolSubscriptionRepository()
@@ -124,20 +124,20 @@ class NostrClient(
}
// Reconnects all relays that may have disconnected
fun connect() {
override fun connect() {
isActive = true
relayPool.connect()
}
fun disconnect() {
override fun disconnect() {
isActive = false
relayPool.disconnect()
}
fun isActive() = isActive
override fun isActive() = isActive
@Synchronized
fun reconnect(onlyIfChanged: Boolean = false) {
override fun reconnect(onlyIfChanged: Boolean) {
if (onlyIfChanged) {
relayPool.reconnectIfNeedsToORIfItIsTime()
} else {
@@ -204,8 +204,8 @@ class NostrClient(
return false
}
fun openReqSubscription(
subId: String = newSubId(),
override fun openReqSubscription(
subId: String,
filters: Map<NormalizedRelayUrl, List<Filter>>,
) {
val oldFilters = activeRequests.getSubscriptionFiltersOrNull(subId) ?: emptyMap()
@@ -240,8 +240,8 @@ class NostrClient(
}
}
fun openCountSubscription(
subId: String = newSubId(),
override fun openCountSubscription(
subId: String,
filters: Map<NormalizedRelayUrl, List<Filter>>,
) {
val oldFilters = activeCounts.getSubscriptionFiltersOrNull(subId) ?: emptyMap()
@@ -276,7 +276,7 @@ class NostrClient(
}
}
fun sendIfExists(
override fun sendIfExists(
event: Event,
connectedRelay: NormalizedRelayUrl,
) {
@@ -288,7 +288,7 @@ class NostrClient(
}
}
fun send(
override fun send(
event: Event,
relayList: Set<NormalizedRelayUrl>,
) {
@@ -301,7 +301,7 @@ class NostrClient(
}
}
fun close(subscriptionId: String) {
override fun close(subscriptionId: String) {
activeRequests.remove(subscriptionId)
activeCounts.remove(subscriptionId)
relayPool.close(subscriptionId)
@@ -386,13 +386,13 @@ class NostrClient(
listeners.forEach { it.onError(relay, subId, error) }
}
fun subscribe(listener: IRelayClientListener) {
override fun subscribe(listener: IRelayClientListener) {
listeners = listeners.plus(listener)
}
fun isSubscribed(listener: IRelayClientListener): Boolean = listeners.contains(listener)
fun unsubscribe(listener: IRelayClientListener) {
override fun unsubscribe(listener: IRelayClientListener) {
listeners = listeners.minus(listener)
}
@@ -402,7 +402,9 @@ class NostrClient(
fun activeOutboxCache(url: NormalizedRelayUrl): Set<HexKey> = eventOutbox.activeOutboxCacheFor(url)
fun getSubscriptionFiltersOrNull(subId: String): Map<NormalizedRelayUrl, List<Filter>>? = activeRequests.getSubscriptionFiltersOrNull(subId)
override fun getReqFiltersOrNull(subId: String): Map<NormalizedRelayUrl, List<Filter>>? = activeRequests.getSubscriptionFiltersOrNull(subId)
fun relayStatusFlow() = relayPool.statusFlow
override fun getCountFiltersOrNull(subId: String): Map<NormalizedRelayUrl, List<Filter>>? = activeCounts.getSubscriptionFiltersOrNull(subId)
override fun relayStatusFlow() = relayPool.statusFlow
}
@@ -28,7 +28,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.utils.RandomInstance
class NostrClientSubscription(
val client: NostrClient,
val client: INostrClient,
val filter: () -> Map<NormalizedRelayUrl, List<Filter>> = { emptyMap() },
val onEvent: (event: Event) -> Unit = {},
) : IRelayClientListener {
@@ -22,7 +22,7 @@ package com.vitorpamplona.quartz.nip01Core.relay.client.accessories
import android.util.Log
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.IRelayClientListener
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
@@ -30,7 +30,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
* Listens to NostrClient's onEvent messages for caching purposes.
*/
class EventCollector(
val client: NostrClient,
val client: INostrClient,
val onEvent: (event: Event, relay: IRelayClient) -> Unit,
) {
private val clientListener =
@@ -22,7 +22,7 @@ package com.vitorpamplona.quartz.nip01Core.relay.client.accessories
import android.util.Log
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.IRelayClientListener
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.RelayState
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
@@ -32,7 +32,7 @@ import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
@OptIn(DelicateCoroutinesApi::class)
suspend fun NostrClient.sendAndWaitForResponse(
suspend fun INostrClient.sendAndWaitForResponse(
event: Event,
relayList: Set<NormalizedRelayUrl>,
timeoutInSeconds: Long = 15,
@@ -21,7 +21,7 @@
package com.vitorpamplona.quartz.nip01Core.relay.client.accessories
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.IRelayClientListener
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
import com.vitorpamplona.quartz.nip01Core.relay.client.single.newSubId
@@ -32,7 +32,7 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
fun NostrClient.downloadFirstEvent(
fun INostrClient.downloadFirstEvent(
subscriptionId: String = newSubId(),
filters: Map<NormalizedRelayUrl, List<Filter>>,
onResponse: (Event) -> Unit,
@@ -21,14 +21,14 @@
package com.vitorpamplona.quartz.nip01Core.relay.client.accessories
import android.util.Log
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.IRelayClientListener
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
class RelayAuthenticator(
val client: NostrClient,
val client: INostrClient,
val scope: CoroutineScope,
val authenticate: suspend (challenge: String, relay: IRelayClient) -> Unit,
) {
@@ -22,7 +22,7 @@ package com.vitorpamplona.quartz.nip01Core.relay.client.accessories
import android.util.Log
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.IRelayClientListener
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
@@ -30,7 +30,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
* Listens to NostrClient's onEvent messages for caching purposes.
*/
class RelayInsertConfirmationCollector(
val client: NostrClient,
val client: INostrClient,
val onRelayReceived: (eventId: HexKey, relay: IRelayClient) -> Unit,
) {
private val clientListener =
@@ -22,7 +22,7 @@ package com.vitorpamplona.quartz.nip01Core.relay.client.accessories
import android.util.Log
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.IRelayClientListener
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
@@ -30,7 +30,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
* Listens to NostrClient's onNotify messages from the relay
*/
class RelayLogger(
val client: NostrClient,
val client: INostrClient,
val notify: (message: String, relay: IRelayClient) -> Unit,
) {
private val clientListener =
@@ -21,7 +21,7 @@
package com.vitorpamplona.quartz.nip01Core.relay.client.accessories
import android.util.Log
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.IRelayClientListener
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
@@ -29,7 +29,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
* Listens to NostrClient's onNotify messages from the relay
*/
class RelayNotifier(
val client: NostrClient,
val client: INostrClient,
val notify: (message: String, relay: IRelayClient) -> Unit,
) {
companion object {
@@ -94,24 +94,31 @@ class RelayPool(
relay.connectAndSyncFiltersIfDisconnected()
}
}
updateStatus()
}
fun connect() =
fun connect() {
relays.forEach { url, relay ->
relay.connect()
}
updateStatus()
}
fun connectIfDisconnected() =
fun connectIfDisconnected() {
relays.forEach { url, relay ->
relay.connectAndSyncFiltersIfDisconnected()
}
updateStatus()
}
fun connectIfDisconnected(relay: NormalizedRelayUrl) = relays.get(relay)?.connectAndSyncFiltersIfDisconnected()
fun disconnect() =
fun disconnect() {
relays.forEach { url, relay ->
relay.disconnect()
}
updateStatus()
}
fun sendRequest(
relay: NormalizedRelayUrl,
@@ -21,7 +21,7 @@
package com.vitorpamplona.quartz.nip01Core.relay.client.subscriptions
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.IRelayClientListener
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
@@ -47,7 +47,7 @@ import com.vitorpamplona.quartz.utils.LargeCache
* - Dismiss subscriptions with [dismissSubscription].
*/
class SubscriptionController(
val client: NostrClient,
val client: INostrClient,
) {
private val subscriptions = LargeCache<String, Subscription>()
private val stats = SubscriptionStats()
@@ -108,7 +108,7 @@ class SubscriptionController(
fun updateRelays() {
val currentFilters =
subscriptions.associateWith { id, sub ->
client.getSubscriptionFiltersOrNull(id)
client.getReqFiltersOrNull(id)
}
subscriptions.forEach { id, sub ->
@@ -61,17 +61,30 @@ class OtsEvent(
const val KIND = 1040
const val ALT = "Opentimestamps Attestation"
/**
* Stamp is used to save OTS requests locally while we want for the bitcoin
* blockchain to include the block with the proof.
*/
fun stamp(
eventId: HexKey,
resolver: OtsResolver,
) = resolver.stamp(eventId.hexToByteArray())
/**
* Converts a stamp into an attestation as soon as the bitcoin chain confirms
* the block with it. If the return is not null, the attestation can be sent
* to Nostr.
*/
fun upgrade(
otsState: ByteArray,
eventId: HexKey,
resolver: OtsResolver,
) = resolver.upgrade(otsState, eventId.hexToByteArray())
/**
* Verifies if the attestation contained in a Nostr Event is valid by checking
* with the blockchain.
*/
fun verify(
otsState: ByteArray,
eventId: HexKey,
@@ -33,14 +33,35 @@ import com.vitorpamplona.quartz.nip03Timestamp.ots.http.CalendarPureJavaBuilder
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpSHA256
import kotlinx.coroutines.CancellationException
/**
* Resolver class for OpenTimestamps operations.
*
* This class provides functionality to stamp data with OpenTimestamps,
* upgrade existing timestamps, and verify timestamp proofs against data.
*
* @property explorer The Bitcoin explorer used for verification operations.
* @property calendarBuilder The calendar builder used for stamping operations.
*/
class OtsResolver(
explorer: BitcoinExplorer = BlockstreamExplorer(),
calendarBuilder: CalendarBuilder = CalendarPureJavaBuilder(),
) {
val ots: OpenTimestamps = OpenTimestamps(explorer, calendarBuilder)
/**
* Returns a human-readable information string about the given OTS file.
*
* @param otsState The serialized OpenTimestamps file data.
* @return A string containing information about the timestamp.
*/
fun info(otsState: ByteArray): String = ots.info(DetachedTimestampFile.deserialize(otsState))
/**
* Creates a local timestamp proof for the given data.
*
* @param data The data to be timestamped.
* @return A serialized DetachedTimestampFile containing the timestamp proof.
*/
fun stamp(data: ByteArray): ByteArray {
val hash = Hash(data, OpSHA256.TAG)
val file = DetachedTimestampFile.from(hash)
@@ -49,6 +70,13 @@ class OtsResolver(
return detachedToSerialize.serialize()
}
/**
* Attempts to upgrade an existing timestamp to a verified proof.
*
* @param otsState The serialized OpenTimestamps file data to upgrade.
* @param data The original data that was timestamped.
* @return A serialized DetachedTimestampFile with upgraded proof if successful, null otherwise.
*/
fun upgrade(
otsState: ByteArray,
data: ByteArray,
@@ -67,11 +95,25 @@ class OtsResolver(
}
}
/**
* Verifies an OpenTimestamps proof against the original data.
*
* @param otsFile The serialized OpenTimestamps file data.
* @param data The original data to verify against.
* @return VerificationState indicating the result of the verification.
*/
fun verify(
otsFile: ByteArray,
data: ByteArray,
): VerificationState = verify(DetachedTimestampFile.deserialize(otsFile), data)
/**
* Verifies an OpenTimestamps proof against the original data.
*
* @param detachedOts The DetachedTimestampFile containing the proof.
* @param data The original data to verify against.
* @return VerificationState indicating the result of the verification.
*/
fun verify(
detachedOts: DetachedTimestampFile,
data: ByteArray,
@@ -23,3 +23,7 @@ package com.vitorpamplona.quartz.nip03Timestamp
interface OtsResolverBuilder {
fun build(): OtsResolver
}
class DefaultOtsResolverBuilder : OtsResolverBuilder {
override fun build(): OtsResolver = OtsResolver()
}
@@ -24,35 +24,31 @@ import android.util.LruCache
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.utils.TimeUtils
class VerificationStateCache {
class VerificationStateCache(
val otsResolverBuilder: OtsResolverBuilder,
) {
private val cache = LruCache<HexKey, VerificationState>(200)
fun verify(
event: OtsEvent,
resolverBuilder: OtsResolverBuilder,
): VerificationState {
fun verify(event: OtsEvent): VerificationState {
cache.put(event.id, VerificationState.Verifying)
return event.verifyState(resolverBuilder.build()).also { cache.put(event.id, it) }
return event.verifyState(otsResolverBuilder.build()).also { cache.put(event.id, it) }
}
fun cacheVerify(
event: OtsEvent,
resolverBuilder: OtsResolverBuilder,
): VerificationState =
fun cacheVerify(event: OtsEvent): VerificationState =
when (val verif = cache[event.id]) {
is VerificationState.Verifying -> verif
is VerificationState.Verified -> verif
is VerificationState.NetworkError -> {
// try again in 5 mins
if (verif.time < TimeUtils.fiveMinutesAgo()) {
event.verifyState(resolverBuilder.build()).also { cache.put(event.id, it) }
event.verifyState(otsResolverBuilder.build()).also { cache.put(event.id, it) }
} else {
verify(event, resolverBuilder)
verify(event)
}
}
is VerificationState.Error -> verif
else -> {
verify(event, resolverBuilder)
verify(event)
}
}
}
@@ -26,26 +26,6 @@ import com.vitorpamplona.quartz.nip03Timestamp.ots.BlockHeader
import java.net.URL
import java.util.concurrent.Executors
/**
* 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.
*/
class BlockstreamExplorer : BitcoinExplorer {
/**
* Retrieve the block information from the block hash.
@@ -50,8 +50,8 @@ class Request(
try {
val httpURLConnection = url.openConnection() as HttpURLConnection
httpURLConnection.setReadTimeout(10000)
httpURLConnection.setConnectTimeout(10000)
httpURLConnection.readTimeout = 10000
httpURLConnection.connectTimeout = 10000
httpURLConnection.setRequestProperty("User-Agent", "OpenTimestamps Java")
httpURLConnection.setRequestProperty("Accept", "application/json")
httpURLConnection.setRequestProperty("Accept-Encoding", "gzip")
@@ -62,17 +62,17 @@ class Request(
if (data != null) {
httpURLConnection.setDoOutput(true)
httpURLConnection.setRequestMethod("POST")
httpURLConnection.requestMethod = "POST"
httpURLConnection.setRequestProperty(
"Content-Length",
"" + this.data!!.size.toString(),
)
val wr = DataOutputStream(httpURLConnection.getOutputStream())
wr.write(this.data, 0, this.data!!.size)
wr.flush()
wr.close()
DataOutputStream(httpURLConnection.getOutputStream()).use { wr ->
wr.write(this.data, 0, this.data!!.size)
wr.flush()
}
} else {
httpURLConnection.setRequestMethod("GET")
httpURLConnection.requestMethod = "GET"
}
httpURLConnection.connect()
@@ -84,7 +84,7 @@ class Request(
response.status = responseCode
response.fromUrl = url.toString()
var `is` = httpURLConnection.getInputStream()
if ("gzip" == httpURLConnection.getContentEncoding()) {
if ("gzip" == httpURLConnection.contentEncoding) {
`is` = GZIPInputStream(`is`)
}
response.setStream(`is`)
@@ -23,6 +23,7 @@ package com.vitorpamplona.quartz.nip03Timestamp.ots.http
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.json.JsonMapper
import java.io.ByteArrayOutputStream
import java.io.Closeable
import java.io.IOException
import java.io.InputStream
import java.nio.charset.StandardCharsets
@@ -30,7 +31,7 @@ import java.nio.charset.StandardCharsets
/**
* Holds the response from an HTTP request.
*/
class Response {
class Response : Closeable {
private var stream: InputStream? = null
var fromUrl: String? = null
@@ -53,7 +54,7 @@ class Response {
@get:Throws(IOException::class)
val string: String
get() = kotlin.text.String(this.bytes, StandardCharsets.UTF_8)
get() = String(this.bytes, StandardCharsets.UTF_8)
@get:Throws(IOException::class)
val bytes: ByteArray
@@ -79,4 +80,9 @@ class Response {
JsonMapper.builder().build()
return builder.readTree(jsonString)
}
override fun close() {
stream?.close()
stream = null
}
}
@@ -62,7 +62,7 @@ abstract class OpCrypto internal constructor() : OpUnary() {
val digest = MessageDigest.getInstance(this.hashLibName())
var chunk = ctx.read(1048576)
while (chunk != null && chunk.size > 0) {
while (chunk.isNotEmpty()) {
digest.update(chunk)
chunk = ctx.read(1048576)
}
@@ -73,7 +73,7 @@ abstract class OpCrypto internal constructor() : OpUnary() {
}
@Throws(IOException::class, NoSuchAlgorithmException::class)
fun hashFd(file: File?): ByteArray = hashFd(FileInputStream(file))
fun hashFd(file: File?): ByteArray = FileInputStream(file).use { inputStream -> hashFd(inputStream) }
@Throws(IOException::class, NoSuchAlgorithmException::class)
fun hashFd(bytes: ByteArray): ByteArray {
@@ -93,6 +93,7 @@ abstract class OpCrypto internal constructor() : OpUnary() {
count = inputStream.read(chunk, 0, 1048576)
}
// TODO: Is this needed? Closing of stream should be callers responsibility?
inputStream.close()
val hash = digest.digest()
@@ -34,6 +34,9 @@ class DecryptZapResponse {
)
fun parse(intent: IntentResult): SignerResult.RequestAddressed<ZapEventDecryptionResult> {
if (intent.rejected) {
return SignerResult.RequestAddressed.ManuallyRejected()
}
val eventJson = intent.result
return if (!eventJson.isNullOrBlank()) {
if (eventJson.startsWith("{")) {
@@ -33,6 +33,9 @@ class DeriveKeyResponse {
)
fun parse(intent: IntentResult): SignerResult.RequestAddressed<DerivationResult> {
if (intent.rejected) {
return SignerResult.RequestAddressed.ManuallyRejected()
}
val newPrivateKey = intent.result
return if (newPrivateKey != null) {
SignerResult.RequestAddressed.Successful(DerivationResult(newPrivateKey))
@@ -32,6 +32,9 @@ class Nip04DecryptResponse {
)
fun parse(intent: IntentResult): SignerResult.RequestAddressed<DecryptionResult> {
if (intent.rejected) {
return SignerResult.RequestAddressed.ManuallyRejected()
}
val plaintext = intent.result
return if (plaintext != null) {
SignerResult.RequestAddressed.Successful(DecryptionResult(plaintext))
@@ -32,6 +32,10 @@ class Nip04EncryptResponse {
)
fun parse(intent: IntentResult): SignerResult.RequestAddressed<EncryptionResult> {
if (intent.rejected) {
return SignerResult.RequestAddressed.ManuallyRejected()
}
val ciphertext = intent.result
return if (ciphertext != null) {
SignerResult.RequestAddressed.Successful(EncryptionResult(ciphertext))
@@ -32,6 +32,9 @@ class Nip44DecryptResponse {
)
fun parse(intent: IntentResult): SignerResult.RequestAddressed<DecryptionResult> {
if (intent.rejected) {
return SignerResult.RequestAddressed.ManuallyRejected()
}
val plaintext = intent.result
return if (plaintext != null) {
SignerResult.RequestAddressed.Successful(DecryptionResult(plaintext))
@@ -32,6 +32,9 @@ class Nip44EncryptResponse {
)
fun parse(intent: IntentResult): SignerResult.RequestAddressed<EncryptionResult> {
if (intent.rejected) {
return SignerResult.RequestAddressed.ManuallyRejected()
}
val ciphertext = intent.result
return if (ciphertext != null) {
SignerResult.RequestAddressed.Successful(EncryptionResult(ciphertext))
@@ -39,6 +39,10 @@ class SignResponse {
intent: IntentResult,
unsignedEvent: Event,
): SignerResult.RequestAddressed<SignResult> {
if (intent.rejected) {
return SignerResult.RequestAddressed.ManuallyRejected()
}
val eventJson = intent.event
return if (eventJson != null) {
if (eventJson.startsWith("{")) {
@@ -29,6 +29,7 @@ data class IntentResult(
val result: String? = null,
val event: String? = null,
val id: String? = null,
val rejected: Boolean = false,
) {
fun toJson(): String = JsonMapper.mapper.writeValueAsString(this)
@@ -48,6 +49,7 @@ data class IntentResult(
result = data.getStringExtra("result"),
event = data.getStringExtra("event"),
`package` = data.getStringExtra("package"),
rejected = data.extras?.containsKey("rejected") == true,
)
fun fromJson(json: String): IntentResult = JsonMapper.mapper.readValue<IntentResult>(json)
@@ -36,6 +36,7 @@ class IntentResultJsonDeserializer : StdDeserializer<IntentResult>(IntentResult:
result = jsonObject.get("result")?.asText()?.intern(),
event = jsonObject.get("event")?.asText()?.intern(),
id = jsonObject.get("id")?.asText()?.intern(),
rejected = jsonObject.get("rejected")?.asBoolean() ?: false,
)
}
}
@@ -35,6 +35,7 @@ class IntentResultJsonSerializer : StdSerializer<IntentResult>(IntentResult::cla
result.result?.let { gen.writeStringField("result", it) }
result.event?.let { gen.writeStringField("event", it) }
result.id?.let { gen.writeStringField("id", it) }
result.rejected.let { gen.writeBooleanField("rejected", it) }
gen.writeEndObject()
}
}
@@ -64,4 +64,6 @@ object TimeUtils {
fun randomWithTwoDays() = now() - RandomInstance.int(twoDays())
fun ninetyDaysFromNow() = now() + NINETY_DAYS
fun oneYearAgo() = now() - ONE_YEAR
}