diff --git a/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.kt b/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.kt index 597f5a3..826c9cd 100644 --- a/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.kt +++ b/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.kt @@ -133,21 +133,20 @@ internal open class DefaultMessageListener( } protected fun receive(objectMessage: ObjectMessage, broadcast: Broadcast) { - val tag = if (broadcast is V5Broadcast) broadcast.tag else null - for (subscription in ctx.addressRepository.getSubscriptions(broadcast.version)) { - if (tag != null && !Arrays.equals(tag, subscription.tag)) { - continue - } - try { - broadcast.decrypt(subscription.publicDecryptionKey) - if (!objectMessage.isSignatureValid(broadcast.plaintext!!.from.pubkey!!)) { - LOG.warn("Broadcast with IV " + objectMessage.inventoryVector + " was successfully decrypted, but signature check failed. Ignoring.") - } else { - receive(objectMessage.inventoryVector, broadcast.plaintext!!) + val tag = (broadcast as? V5Broadcast)?.tag + ctx.addressRepository.getSubscriptions(broadcast.version) + .filter { tag == null || Arrays.equals(tag, it.tag) } + .forEach { + try { + broadcast.decrypt(it.publicDecryptionKey) + if (!objectMessage.isSignatureValid(broadcast.plaintext!!.from.pubkey!!)) { + LOG.warn("Broadcast with IV " + objectMessage.inventoryVector + " was successfully decrypted, but signature check failed. Ignoring.") + } else { + receive(objectMessage.inventoryVector, broadcast.plaintext!!) + } + } catch (_: DecryptionFailedException) { } - } catch (_: DecryptionFailedException) { } - } } protected fun receive(iv: InventoryVector, msg: Plaintext) { diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.kt b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.kt index 462ae83..3dcf0dc 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.kt @@ -36,14 +36,14 @@ import java.util.* import java.util.Collections import kotlin.collections.HashSet -fun message(encoding: Plaintext.Encoding, subject: String, body: String): ByteArray = when (encoding) { +internal fun message(encoding: Plaintext.Encoding, subject: String, body: String): ByteArray = when (encoding) { SIMPLE -> "Subject:$subject\nBody:$body".toByteArray() EXTENDED -> Message.Builder().subject(subject).body(body).build().zip() TRIVIAL -> (subject + body).toByteArray() IGNORE -> ByteArray(0) } -fun ackData(type: Plaintext.Type, ackData: ByteArray?): ByteArray? { +internal fun ackData(type: Plaintext.Type, ackData: ByteArray?): ByteArray? { if (ackData != null) { return ackData } else if (type == MSG) { @@ -67,6 +67,7 @@ class Plaintext private constructor( val conversationId: UUID = UUID.randomUUID(), var inventoryVector: InventoryVector? = null, var signature: ByteArray? = null, + sent: Long? = null, val received: Long? = null, var initialHash: ByteArray? = null, ttl: Long = TTL.msg, @@ -117,7 +118,7 @@ class Plaintext private constructor( } val encoding: Encoding? by lazy { Encoding.fromCode(encodingCode) } - var sent: Long? = null + var sent: Long? = sent private set var retries: Int = 0 private set @@ -145,9 +146,9 @@ class Plaintext private constructor( type = type, from = from, to = to, - encoding = encoding.code, + encodingCode = encoding.code, message = message, - ackMessage = ackData(type, ackData), + ackData = ackData(type, ackData), conversationId = conversationId, inventoryVector = inventoryVector, signature = signature, @@ -214,9 +215,9 @@ class Plaintext private constructor( type = type, from = from, to = to, - encoding = encoding.code, + encoding = encoding, message = message(encoding, subject, body), - ackMessage = ackData(type, ackData), + ackData = ackData(type, ackData), conversationId = conversationId, inventoryVector = null, signature = null, @@ -248,6 +249,7 @@ class Plaintext private constructor( conversationId = builder.conversation ?: UUID.randomUUID(), inventoryVector = builder.inventoryVector, signature = builder.signature, + sent = builder.sent, received = builder.received, initialHash = null, ttl = builder.ttl, diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.kt index d6df388..29f26fe 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.kt @@ -195,7 +195,7 @@ class CryptoBox : Streamable { companion object { private val LOG = LoggerFactory.getLogger(CryptoBox::class.java) - fun read(stream: InputStream, length: Int): CryptoBox { + @JvmStatic fun read(stream: InputStream, length: Int): CryptoBox { val counter = AccessCounter() return Builder() .IV(Decode.bytes(stream, 16, counter)) diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.kt index 336630a..10f0448 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.kt @@ -53,7 +53,7 @@ class GenericPayload(version: Long, override val stream: Long, val data: ByteArr } companion object { - fun read(version: Long, stream: Long, `is`: InputStream, length: Int): GenericPayload { + @JvmStatic fun read(version: Long, stream: Long, `is`: InputStream, length: Int): GenericPayload { return GenericPayload(version, stream, Decode.bytes(`is`, length)) } } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.kt index 3ae9370..e218d6d 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.kt @@ -58,7 +58,7 @@ class GetPubkey : ObjectPayload { } companion object { - fun read(`is`: InputStream, stream: Long, length: Int, version: Long): GetPubkey { + @JvmStatic fun read(`is`: InputStream, stream: Long, length: Int, version: Long): GetPubkey { return GetPubkey(version, stream, Decode.bytes(`is`, length)) } } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.kt index 597220e..c2ebd8b 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.kt @@ -96,7 +96,7 @@ class Msg : ObjectPayload, Encrypted, PlaintextHolder { companion object { val ACK_LENGTH = 32 - fun read(`in`: InputStream, stream: Long, length: Int): Msg { + @JvmStatic fun read(`in`: InputStream, stream: Long, length: Int): Msg { return Msg(stream, CryptoBox.read(`in`, length)) } } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectType.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectType.kt index 967a3fd..ec7065a 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectType.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectType.kt @@ -26,7 +26,7 @@ enum class ObjectType constructor(val number: Long) { BROADCAST(3); companion object { - fun fromNumber(number: Long): ObjectType? { + @JvmStatic fun fromNumber(number: Long): ObjectType? { return values().firstOrNull { it.number == number } } } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.kt index 28acf6f..c1c7253 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.kt @@ -80,7 +80,7 @@ open class V2Pubkey constructor(version: Long, override val stream: Long, overri } companion object { - fun read(`in`: InputStream, stream: Long): V2Pubkey { + @JvmStatic fun read(`in`: InputStream, stream: Long): V2Pubkey { return V2Pubkey( version = 2, stream = stream, diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.kt index 80dec2b..53faed8 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.kt @@ -134,7 +134,7 @@ class V3Pubkey protected constructor( } companion object { - fun read(`is`: InputStream, stream: Long): V3Pubkey { + @JvmStatic fun read(`is`: InputStream, stream: Long): V3Pubkey { return V3Pubkey( version = 3, stream = stream, diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.kt index b5ef096..e270754 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.kt @@ -52,7 +52,7 @@ open class V4Broadcast : Broadcast { } companion object { - fun read(`in`: InputStream, stream: Long, length: Int): V4Broadcast { + @JvmStatic fun read(`in`: InputStream, stream: Long, length: Int): V4Broadcast { return V4Broadcast(4, stream, CryptoBox.read(`in`, length), null) } } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.kt index d755864..1fc1196 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.kt @@ -127,7 +127,7 @@ class V4Pubkey : Pubkey, Encrypted { } companion object { - fun read(`in`: InputStream, stream: Long, length: Int, encrypted: Boolean): V4Pubkey { + @JvmStatic fun read(`in`: InputStream, stream: Long, length: Int, encrypted: Boolean): V4Pubkey { if (encrypted) return V4Pubkey(stream, Decode.bytes(`in`, 32), diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.kt index 4ab9af9..68b7454 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.kt @@ -51,7 +51,7 @@ class V5Broadcast : V4Broadcast { } companion object { - fun read(`is`: InputStream, stream: Long, length: Int): V5Broadcast { + @JvmStatic fun read(`is`: InputStream, stream: Long, length: Int): V5Broadcast { return V5Broadcast(stream, Decode.bytes(`is`, 32), CryptoBox.read(`is`, length - 32)) } } diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Property.kt b/core/src/main/java/ch/dissem/bitmessage/utils/Property.kt index bea1621..323c5cb 100644 --- a/core/src/main/java/ch/dissem/bitmessage/utils/Property.kt +++ b/core/src/main/java/ch/dissem/bitmessage/utils/Property.kt @@ -28,7 +28,8 @@ package ch.dissem.bitmessage.utils class Property private constructor(val name: String, val value: Any? = null, val properties: Array = emptyArray()) { constructor(name: String, value: Any) : this(name = name, value = value, properties = emptyArray()) - constructor(name: String, vararg properties: Property) : this(name, null, Array(properties.size, { i -> properties[i] })) + constructor(name: String, vararg properties: Property) : this(name, null, arrayOf(*properties)) + constructor(name: String, properties: List) : this(name, null, properties.toTypedArray()) /** * Returns the property if available or `null` otherwise. diff --git a/cryptography-sc/src/main/kotlin/ch/dissem/bitmessage/cryptography/sc/SpongyCryptography.kt b/cryptography-sc/src/main/kotlin/ch/dissem/bitmessage/cryptography/sc/SpongyCryptography.kt index 1465fb3..1db64b3 100644 --- a/cryptography-sc/src/main/kotlin/ch/dissem/bitmessage/cryptography/sc/SpongyCryptography.kt +++ b/cryptography-sc/src/main/kotlin/ch/dissem/bitmessage/cryptography/sc/SpongyCryptography.kt @@ -40,7 +40,7 @@ import java.util.* * As Spongycastle can't be used on the Oracle JVM, and Bouncycastle doesn't work properly on Android (thanks, Google), * this is the Spongycastle implementation. */ -class SpongyCryptography : AbstractCryptography(BouncyCastleProvider()) { +open class SpongyCryptography : AbstractCryptography(BouncyCastleProvider()) { override fun crypt(encrypt: Boolean, data: ByteArray, key_e: ByteArray, initializationVector: ByteArray): ByteArray { val cipher = PaddedBufferedBlockCipher( diff --git a/demo/build.gradle b/demo/build.gradle index 159dbc0..a00dcf4 100644 --- a/demo/build.gradle +++ b/demo/build.gradle @@ -33,5 +33,5 @@ dependencies { compile 'com.h2database:h2:1.4.194' compile 'org.apache.commons:commons-lang3:3.5' testCompile 'junit:junit:4.12' - testCompile 'org.mockito:mockito-core:2.7.21' + testCompile 'com.nhaarman:mockito-kotlin:1.4.0' } diff --git a/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java b/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java index 224cc1a..2cfe84f 100644 --- a/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java +++ b/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java @@ -22,13 +22,11 @@ import ch.dissem.bitmessage.entity.Plaintext; import ch.dissem.bitmessage.networking.nio.NioNetworkHandler; import ch.dissem.bitmessage.ports.DefaultLabeler; import ch.dissem.bitmessage.ports.Labeler; -import ch.dissem.bitmessage.ports.NetworkHandler; import ch.dissem.bitmessage.repository.*; import ch.dissem.bitmessage.utils.TTL; import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.mockito.Mockito; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,6 +35,9 @@ import java.util.concurrent.TimeUnit; import static ch.dissem.bitmessage.entity.payload.Pubkey.Feature.DOES_ACK; import static ch.dissem.bitmessage.utils.UnixTime.MINUTE; +import static com.nhaarman.mockito_kotlin.MockitoKt.spy; +import static com.nhaarman.mockito_kotlin.MockitoKt.timeout; +import static com.nhaarman.mockito_kotlin.MockitoKt.verify; import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -65,7 +66,7 @@ public class SystemTest { int bobPort = port++; { JdbcConfig aliceDB = new JdbcConfig("jdbc:h2:mem:alice;DB_CLOSE_DELAY=-1", "sa", ""); - aliceLabeler = Mockito.spy(new DebugLabeler("Alice")); + aliceLabeler = spy(new DebugLabeler("Alice")); TestListener aliceListener = new TestListener(); alice = new BitmessageContext.Builder() .addressRepo(new JdbcAddressRepository(aliceDB)) @@ -110,17 +111,17 @@ public class SystemTest { bob.shutdown(); } - @Test(timeout = 60_000) + @Test(timeout = 120_000) public void ensureAliceCanSendMessageToBob() throws Exception { String originalMessage = UUID.randomUUID().toString(); alice.send(aliceIdentity, new BitmessageAddress(bobIdentity.getAddress()), "Subject", originalMessage); - Plaintext plaintext = bobListener.get(15, TimeUnit.MINUTES); + Plaintext plaintext = bobListener.get(2, TimeUnit.MINUTES); assertThat(plaintext.getType(), equalTo(Plaintext.Type.MSG)); assertThat(plaintext.getText(), equalTo(originalMessage)); - Mockito.verify(aliceLabeler, Mockito.timeout(TimeUnit.MINUTES.toMillis(15)).atLeastOnce()) + verify(aliceLabeler, timeout(TimeUnit.MINUTES.toMillis(2)).atLeastOnce()) .markAsAcknowledged(any()); } diff --git a/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/Connection.kt b/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/Connection.kt index 03552b6..22293c9 100644 --- a/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/Connection.kt +++ b/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/Connection.kt @@ -75,7 +75,7 @@ class Connection( fun send(payload: MessagePayload) = io.send(payload) - protected fun handleMessage(payload: MessagePayload) { + private fun handleMessage(payload: MessagePayload) { when (state) { State.CONNECTING -> initializer!!.handleCommand(payload) State.ACTIVE -> receiveMessage(payload) @@ -108,7 +108,7 @@ class Connection( private fun receiveMessage(objectMessage: ObjectMessage) { requestedObjects.remove(objectMessage.inventoryVector) if (ctx.inventory.contains(objectMessage)) { - LOG.trace("Received object " + objectMessage.inventoryVector + " - already in inventory") + LOG.trace("Received object ${objectMessage.inventoryVector} - already in inventory") return } try { @@ -122,7 +122,7 @@ class Connection( LOG.warn(e.message) // DebugUtils.saveToFile(objectMessage); // this line must not be committed active } catch (e: IOException) { - LOG.error("Stream " + objectMessage.stream + ", object type " + objectMessage.type + ": " + e.message, e) + LOG.error("Stream ${objectMessage.stream}, object type ${objectMessage.type}: ${e.message}", e) } finally { if (commonRequestedObjects.remove(objectMessage.inventoryVector) == null) { LOG.debug("Received object that wasn't requested.") @@ -131,7 +131,7 @@ class Connection( } private fun receiveMessage(addr: Addr) { - LOG.trace("Received " + addr.addresses.size + " addresses.") + LOG.trace("Received ${addr.addresses.size} addresses.") ctx.nodeRegistry.offerAddresses(addr.addresses) } diff --git a/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.kt b/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.kt index 8900b49..171b4bb 100644 --- a/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.kt +++ b/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.kt @@ -431,7 +431,7 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder { } return Property("network", Property("connectionManager", if (isRunning) "running" else "stopped"), - Property("connections", *streamProperties.toTypedArray()), + Property("connections", streamProperties), Property("requestedObjects", requestedObjects.size) ) } diff --git a/networking/src/test/java/ch/dissem/bitmessage/networking/TestNodeRegistry.java b/networking/src/test/java/ch/dissem/bitmessage/networking/TestNodeRegistry.java deleted file mode 100644 index fb00b26..0000000 --- a/networking/src/test/java/ch/dissem/bitmessage/networking/TestNodeRegistry.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.networking; - -import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; -import ch.dissem.bitmessage.ports.NodeRegistry; - -import java.util.Arrays; -import java.util.List; - -/** - * Empty {@link NodeRegistry} that doesn't do anything, but shouldn't break things either. - */ -class TestNodeRegistry implements NodeRegistry { - private List nodes; - - public TestNodeRegistry(NetworkAddress... nodes) { - this.nodes = Arrays.asList(nodes); - } - - @Override - public void clear() { - // no op - } - - @Override - public List getKnownAddresses(int limit, long... streams) { - return nodes; - } - - @Override - public void offerAddresses(List addresses) { - // Ignore - } -} diff --git a/networking/src/test/java/ch/dissem/bitmessage/networking/TestNodeRegistry.kt b/networking/src/test/java/ch/dissem/bitmessage/networking/TestNodeRegistry.kt new file mode 100644 index 0000000..3c00bf4 --- /dev/null +++ b/networking/src/test/java/ch/dissem/bitmessage/networking/TestNodeRegistry.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2015 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.networking + +import ch.dissem.bitmessage.entity.valueobject.NetworkAddress +import ch.dissem.bitmessage.ports.NodeRegistry + +import java.util.Arrays + +/** + * Empty [NodeRegistry] that doesn't do anything, but shouldn't break things either. + */ +internal class TestNodeRegistry(vararg nodes: NetworkAddress) : NodeRegistry { + private val nodes: List = listOf(*nodes) + + override fun clear() { + // no op + } + + override fun getKnownAddresses(limit: Int, vararg streams: Long): List { + return nodes + } + + override fun offerAddresses(addresses: List) { + // Ignore + } +} diff --git a/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcMessageRepository.kt b/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcMessageRepository.kt index 6daf32c..ad5e834 100644 --- a/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcMessageRepository.kt +++ b/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcMessageRepository.kt @@ -98,7 +98,7 @@ class JdbcMessageRepository(private val config: JdbcConfig) : AbstractMessageRep builder.id(id) builder.IV(InventoryVector.fromHash(iv)) builder.from(ctx.addressRepository.getAddress(rs.getString("sender"))!!) - builder.to(ctx.addressRepository.getAddress(rs.getString("recipient"))) + rs.getString("recipient")?.let { builder.to(ctx.addressRepository.getAddress(it)) } builder.ackData(rs.getBytes("ack_data")) builder.sent(rs.getObject("sent") as Long?) builder.received(rs.getObject("received") as Long?)