diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/Nip19.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/Nip19.kt index f4b551be1..54d765940 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/Nip19.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/Nip19.kt @@ -1,71 +1,103 @@ package com.vitorpamplona.amethyst.service -import com.vitorpamplona.amethyst.model.toByteArray import com.vitorpamplona.amethyst.model.toHexKey -import com.vitorpamplona.amethyst.service.model.ATag +import nostr.postr.bechToBytes import java.nio.ByteBuffer import java.nio.ByteOrder -import nostr.postr.Bech32 -import nostr.postr.bechToBytes -import nostr.postr.toByteArray class Nip19 { enum class Type { USER, NOTE, RELAY, ADDRESS } + data class Return(val type: Type, val hex: String) fun uriToRoute(uri: String?): Return? { try { - val key = uri?.removePrefix("nostr:") + val key = uri?.removePrefix("nostr:") ?: return null - if (key != null) { - val bytes = key.bechToBytes() - if (key.startsWith("npub")) { - return Return(Type.USER, bytes.toHexKey()) - } - if (key.startsWith("note")) { - return Return(Type.NOTE, bytes.toHexKey()) - } - if (key.startsWith("nprofile")) { - val tlv = parseTLV(bytes) - val hex = tlv.get(NIP19TLVTypes.SPECIAL.id)?.get(0)?.toHexKey() - if (hex != null) - return Return(Type.USER, hex) - } - if (key.startsWith("nevent")) { - val tlv = parseTLV(bytes) - val hex = tlv.get(NIP19TLVTypes.SPECIAL.id)?.get(0)?.toHexKey() - if (hex != null) - return Return(Type.USER, hex) - } - if (key.startsWith("nrelay")) { - val tlv = parseTLV(bytes) - val relayUrl = tlv.get(NIP19TLVTypes.SPECIAL.id)?.get(0)?.toString(Charsets.UTF_8) - if (relayUrl != null) - return Return(Type.RELAY, relayUrl) - } - if (key.startsWith("naddr")) { - val tlv = parseTLV(bytes) - val d = tlv.get(NIP19TLVTypes.SPECIAL.id)?.get(0)?.toString(Charsets.UTF_8) - val relay = tlv.get(NIP19TLVTypes.RELAY.id)?.get(0)?.toString(Charsets.UTF_8) - val author = tlv.get(NIP19TLVTypes.AUTHOR.id)?.get(0)?.toHexKey() - val kind = tlv.get(NIP19TLVTypes.KIND.id)?.get(0)?.let { toInt32(it) } - if (d != null) - return Return(Type.ADDRESS, "$kind:$author:$d") - } + val bytes = key.bechToBytes() + if (key.startsWith("npub")) { + return npub(bytes) + } else if (key.startsWith("note")) { + return note(bytes) + } else if (key.startsWith("nprofile")) { + return nprofile(bytes) + } else if (key.startsWith("nevent")) { + return nevent(bytes) + } else if (key.startsWith("nrelay")) { + return nrelay(bytes) + } else if (key.startsWith("naddr")) { + return naddr(bytes) } } catch (e: Throwable) { println("Issue trying to Decode NIP19 ${uri}: ${e.message}") - //e.printStackTrace() } return null } + + private fun npub(bytes: ByteArray): Return { + return Return(Type.USER, bytes.toHexKey()) + } + + private fun note(bytes: ByteArray): Return { + return Return(Type.NOTE, bytes.toHexKey()); + } + + private fun nprofile(bytes: ByteArray): Return? { + val hex = parseTLV(bytes) + .get(NIP19TLVTypes.SPECIAL.id) + ?.get(0) + ?.toHexKey() ?: return null + + return Return(Type.USER, hex) + } + + private fun nevent(bytes: ByteArray): Return? { + val hex = parseTLV(bytes) + .get(NIP19TLVTypes.SPECIAL.id) + ?.get(0) + ?.toHexKey() ?: return null + + return Return(Type.USER, hex) + } + + private fun nrelay(bytes: ByteArray): Return? { + val relayUrl = parseTLV(bytes) + .get(NIP19TLVTypes.SPECIAL.id) + ?.get(0) + ?.toString(Charsets.UTF_8) ?: return null + + return Return(Type.RELAY, relayUrl) + } + + private fun naddr(bytes: ByteArray): Return? { + val tlv = parseTLV(bytes) + + val d = tlv.get(NIP19TLVTypes.SPECIAL.id) + ?.get(0) + ?.toString(Charsets.UTF_8) ?: return null + + val relay = tlv.get(NIP19TLVTypes.RELAY.id) + ?.get(0) + ?.toString(Charsets.UTF_8) + + val author = tlv.get(NIP19TLVTypes.AUTHOR.id) + ?.get(0) + ?.toHexKey() + + val kind = tlv.get(NIP19TLVTypes.KIND.id) + ?.get(0) + ?.let { toInt32(it) } + + return Return(Type.ADDRESS, "$kind:$author:$d") + } } -enum class NIP19TLVTypes(val id: Byte) { //classes should start with an uppercase letter in kotlin +// Classes should start with an uppercase letter in kotlin +enum class NIP19TLVTypes(val id: Byte) { SPECIAL(0), RELAY(1), AUTHOR(2), @@ -78,19 +110,19 @@ fun toInt32(bytes: ByteArray): Int { } fun parseTLV(data: ByteArray): Map> { - var result = mutableMapOf>() + val result = mutableMapOf>() var rest = data while (rest.isNotEmpty()) { val t = rest[0] val l = rest[1] val v = rest.sliceArray(IntRange(2, (2 + l) - 1)) - rest = rest.sliceArray(IntRange(2 + l, rest.size-1)) + rest = rest.sliceArray(IntRange(2 + l, rest.size - 1)) if (v.size < l) continue if (!result.containsKey(t)) { - result.put(t, mutableListOf()) + result[t] = mutableListOf() } - result.get(t)?.add(v) + result[t]?.add(v) } return result } diff --git a/app/src/test/java/com/vitorpamplona/amethyst/service/Nip19Test.kt b/app/src/test/java/com/vitorpamplona/amethyst/service/Nip19Test.kt new file mode 100644 index 000000000..53de03970 --- /dev/null +++ b/app/src/test/java/com/vitorpamplona/amethyst/service/Nip19Test.kt @@ -0,0 +1,109 @@ +package com.vitorpamplona.amethyst.service + +import org.junit.Assert +import org.junit.Ignore +import org.junit.Test + +class Nip19Test { + + private val nip19 = Nip19(); + + @Test(expected = IllegalArgumentException::class) + fun to_int_32_length_smaller_than_4() { + toInt32(byteArrayOfInts(1, 2, 3)) + } + + @Test(expected = IllegalArgumentException::class) + fun to_int_32_length_bigger_than_4() { + toInt32(byteArrayOfInts(1, 2, 3, 4, 5)) + } + + @Test() + fun to_int_32_length_4() { + val actual = toInt32(byteArrayOfInts(1, 2, 3, 4)) + + Assert.assertEquals(16909060, actual) + } + + @Ignore("Test not implemented yet") + @Test() + fun parse_TLV() { + // TODO: I don't know how to test this (?) + } + + @Test() + fun uri_to_route_null() { + val actual = nip19.uriToRoute(null) + + Assert.assertEquals(null, actual) + } + + @Test() + fun uri_to_route_unknown() { + val actual = nip19.uriToRoute("nostr:unknown") + + Assert.assertEquals(null, actual) + } + + @Test() + fun uri_to_route_npub() { + val actual = + nip19.uriToRoute("nostr:npub1hv7k2s755n697sptva8vkh9jz40lzfzklnwj6ekewfmxp5crwdjs27007y") + + Assert.assertEquals(Nip19.Type.USER, actual?.type) + Assert.assertEquals( + "bb3d6543d4a4f45f402b674ecb5cb2155ff12456fcdd2d66d9727660d3037365", + actual?.hex + ) + } + + @Test() + fun uri_to_route_note() { + val actual = + nip19.uriToRoute("nostr:note1stqea6wmwezg9x6yyr6qkukw95ewtdukyaztycws65l8wppjmtpscawevv") + + Assert.assertEquals(Nip19.Type.NOTE, actual?.type) + Assert.assertEquals( + "82c19ee9db7644829b4420f40b72ce2d32e5b7962744b261d0d53e770432dac3", + actual?.hex + ) + } + + @Ignore("Test not implemented yet") + @Test() + fun uri_to_route_nprofile() { + val actual = nip19.uriToRoute("nostr:nprofile") + + Assert.assertEquals(Nip19.Type.USER, actual?.type) + Assert.assertEquals("*", actual?.hex) + } + + @Ignore("Test not implemented yet") + @Test() + fun uri_to_route_nevent() { + val actual = nip19.uriToRoute("nostr:nevent") + + Assert.assertEquals(Nip19.Type.USER, actual?.type) + Assert.assertEquals("*", actual?.hex) + } + + @Ignore("Test not implemented yet") + @Test() + fun uri_to_route_nrelay() { + val actual = nip19.uriToRoute("nostr:nrelay") + + Assert.assertEquals(Nip19.Type.RELAY, actual?.type) + Assert.assertEquals("*", actual?.hex) + } + + @Ignore("Test not implemented yet") + @Test() + fun uri_to_route_naddr() { + val actual = nip19.uriToRoute("nostr:naddr") + + Assert.assertEquals(Nip19.Type.ADDRESS, actual?.type) + Assert.assertEquals("*", actual?.hex) + } + + private fun byteArrayOfInts(vararg ints: Int) = ByteArray(ints.size) { pos -> ints[pos].toByte() } +}