Fixes and improvements, SystemTest still broken
This commit is contained in:
		| @@ -84,7 +84,7 @@ BitmessageContext ctx = new BitmessageContext.Builder() | |||||||
|         .powRepo(new JdbcProofOfWorkRepository(jdbcConfig)) |         .powRepo(new JdbcProofOfWorkRepository(jdbcConfig)) | ||||||
|         .nodeRegistry(new JdbcNodeRegistry(jdbcConfig)) |         .nodeRegistry(new JdbcNodeRegistry(jdbcConfig)) | ||||||
|         .networkHandler(new NioNetworkHandler()) |         .networkHandler(new NioNetworkHandler()) | ||||||
|         .cryptography(BouncyCryptography.INSTANCE) |         .cryptography(new BouncyCryptography()) | ||||||
|         .listener(System.out::println) |         .listener(System.out::println) | ||||||
|         .build(); |         .build(); | ||||||
| ``` | ``` | ||||||
|   | |||||||
| @@ -29,6 +29,7 @@ subprojects { | |||||||
|     } |     } | ||||||
|     dependencies { |     dependencies { | ||||||
|         compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" |         compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" | ||||||
|  |         compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     test { |     test { | ||||||
|   | |||||||
| @@ -58,7 +58,53 @@ import kotlin.properties.Delegates | |||||||
|  * |  * | ||||||
|  * The port defaults to 8444 (the default Bitmessage port) |  * The port defaults to 8444 (the default Bitmessage port) | ||||||
|  */ |  */ | ||||||
| class BitmessageContext private constructor(builder: BitmessageContext.Builder) { | class BitmessageContext( | ||||||
|  |     cryptography: Cryptography, | ||||||
|  |     inventory: Inventory, | ||||||
|  |     nodeRegistry: NodeRegistry, | ||||||
|  |     networkHandler: NetworkHandler, | ||||||
|  |     addressRepository: AddressRepository, | ||||||
|  |     messageRepository: MessageRepository, | ||||||
|  |     proofOfWorkRepository: ProofOfWorkRepository, | ||||||
|  |     proofOfWorkEngine: ProofOfWorkEngine = MultiThreadedPOWEngine(), | ||||||
|  |     customCommandHandler: CustomCommandHandler = object : CustomCommandHandler { | ||||||
|  |         override fun handle(request: CustomMessage): MessagePayload? { | ||||||
|  |             BitmessageContext.LOG.debug("Received custom request, but no custom command handler configured.") | ||||||
|  |             return null | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |     listener: Listener, | ||||||
|  |     labeler: Labeler = DefaultLabeler(), | ||||||
|  |     port: Int = 8444, | ||||||
|  |     connectionTTL: Long = 30 * MINUTE, | ||||||
|  |     connectionLimit: Int = 150, | ||||||
|  |     sendPubkeyOnIdentityCreation: Boolean, | ||||||
|  |     doMissingProofOfWorkDelayInSeconds: Int = 30 | ||||||
|  | ) { | ||||||
|  |  | ||||||
|  |     private constructor(builder: BitmessageContext.Builder) : this( | ||||||
|  |         builder.cryptography, | ||||||
|  |         builder.inventory, | ||||||
|  |         builder.nodeRegistry, | ||||||
|  |         builder.networkHandler, | ||||||
|  |         builder.addressRepo, | ||||||
|  |         builder.messageRepo, | ||||||
|  |         builder.proofOfWorkRepository, | ||||||
|  |         builder.proofOfWorkEngine ?: MultiThreadedPOWEngine(), | ||||||
|  |         builder.customCommandHandler ?: object : CustomCommandHandler { | ||||||
|  |             override fun handle(request: CustomMessage): MessagePayload? { | ||||||
|  |                 BitmessageContext.LOG.debug("Received custom request, but no custom command handler configured.") | ||||||
|  |                 return null | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         builder.listener, | ||||||
|  |         builder.labeler ?: DefaultLabeler(), | ||||||
|  |         builder.port, | ||||||
|  |         builder.connectionTTL, | ||||||
|  |         builder.connectionLimit, | ||||||
|  |         builder.sendPubkeyOnIdentityCreation, | ||||||
|  |         builder.doMissingProofOfWorkDelay | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     private val sendPubkeyOnIdentityCreation: Boolean |     private val sendPubkeyOnIdentityCreation: Boolean | ||||||
|  |  | ||||||
| @@ -72,38 +118,10 @@ class BitmessageContext private constructor(builder: BitmessageContext.Builder) | |||||||
|     val labeler: Labeler |     val labeler: Labeler | ||||||
|         @JvmName("labeler") get |         @JvmName("labeler") get | ||||||
|  |  | ||||||
|     init { |     val addresses: AddressRepository | ||||||
|         labeler = builder.labeler ?: DefaultLabeler() |  | ||||||
|         internals = InternalContext( |  | ||||||
|             builder.cryptography, |  | ||||||
|             builder.inventory, |  | ||||||
|             builder.nodeRegistry, |  | ||||||
|             builder.networkHandler, |  | ||||||
|             builder.addressRepo, |  | ||||||
|             builder.messageRepo, |  | ||||||
|             builder.proofOfWorkRepository, |  | ||||||
|             builder.proofOfWorkEngine ?: MultiThreadedPOWEngine(), |  | ||||||
|             builder.customCommandHandler ?: object : CustomCommandHandler { |  | ||||||
|                 override fun handle(request: CustomMessage): MessagePayload? { |  | ||||||
|                     LOG.debug("Received custom request, but no custom command handler configured.") |  | ||||||
|                     return null |  | ||||||
|                 } |  | ||||||
|             }, |  | ||||||
|             builder.listener, |  | ||||||
|             labeler, |  | ||||||
|             builder.port, |  | ||||||
|             builder.connectionTTL, |  | ||||||
|             builder.connectionLimit |  | ||||||
|         ) |  | ||||||
|         internals.proofOfWorkService.doMissingProofOfWork(30000) // TODO: this should be configurable |  | ||||||
|         sendPubkeyOnIdentityCreation = builder.sendPubkeyOnIdentityCreation |  | ||||||
|         (builder.listener as? Listener.WithContext)?.setContext(this) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     val addresses: AddressRepository = internals.addressRepository |  | ||||||
|         @JvmName("addresses") get |         @JvmName("addresses") get | ||||||
|  |  | ||||||
|     val messages: MessageRepository = internals.messageRepository |     val messages: MessageRepository | ||||||
|         @JvmName("messages") get |         @JvmName("messages") get | ||||||
|  |  | ||||||
|     fun createIdentity(shorter: Boolean, vararg features: Feature): BitmessageAddress { |     fun createIdentity(shorter: Boolean, vararg features: Feature): BitmessageAddress { | ||||||
| @@ -285,13 +303,12 @@ class BitmessageContext private constructor(builder: BitmessageContext.Builder) | |||||||
|     fun addContact(contact: BitmessageAddress) { |     fun addContact(contact: BitmessageAddress) { | ||||||
|         internals.addressRepository.save(contact) |         internals.addressRepository.save(contact) | ||||||
|         if (contact.pubkey == null) { |         if (contact.pubkey == null) { | ||||||
|             internals.addressRepository.getAddress(contact.address)?.let { |             // If it already existed, the saved contact might have the public key | ||||||
|                 if (it.pubkey == null) { |             if (internals.addressRepository.getAddress(contact.address)!!.pubkey == null) { | ||||||
|                 internals.requestPubkey(contact) |                 internals.requestPubkey(contact) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun addSubscribtion(address: BitmessageAddress) { |     fun addSubscribtion(address: BitmessageAddress) { | ||||||
|         address.isSubscribed = true |         address.isSubscribed = true | ||||||
| @@ -300,13 +317,13 @@ class BitmessageContext private constructor(builder: BitmessageContext.Builder) | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun tryToFindBroadcastsForAddress(address: BitmessageAddress) { |     private fun tryToFindBroadcastsForAddress(address: BitmessageAddress) { | ||||||
|         for (`object` in internals.inventory.getObjects(address.stream, Broadcast.getVersion(address), ObjectType.BROADCAST)) { |         for (objectMessage in internals.inventory.getObjects(address.stream, Broadcast.getVersion(address), ObjectType.BROADCAST)) { | ||||||
|             try { |             try { | ||||||
|                 val broadcast = `object`.payload as Broadcast |                 val broadcast = objectMessage.payload as Broadcast | ||||||
|                 broadcast.decrypt(address) |                 broadcast.decrypt(address) | ||||||
|                 // This decrypts it twice, but on the other hand it doesn't try to decrypt the objects with |                 // This decrypts it twice, but on the other hand it doesn't try to decrypt the objects with | ||||||
|                 // other subscriptions and the interface stays as simple as possible. |                 // other subscriptions and the interface stays as simple as possible. | ||||||
|                 internals.networkListener.receive(`object`) |                 internals.networkListener.receive(objectMessage) | ||||||
|             } catch (ignore: DecryptionFailedException) { |             } catch (ignore: DecryptionFailedException) { | ||||||
|             } catch (e: Exception) { |             } catch (e: Exception) { | ||||||
|                 LOG.debug(e.message, e) |                 LOG.debug(e.message, e) | ||||||
| @@ -348,6 +365,7 @@ class BitmessageContext private constructor(builder: BitmessageContext.Builder) | |||||||
|         internal var connectionLimit = 150 |         internal var connectionLimit = 150 | ||||||
|         internal var connectionTTL = 30 * MINUTE |         internal var connectionTTL = 30 * MINUTE | ||||||
|         internal var sendPubkeyOnIdentityCreation = true |         internal var sendPubkeyOnIdentityCreation = true | ||||||
|  |         internal var doMissingProofOfWorkDelay = 30 | ||||||
|  |  | ||||||
|         fun port(port: Int): Builder { |         fun port(port: Int): Builder { | ||||||
|             this.port = port |             this.port = port | ||||||
| @@ -409,6 +427,7 @@ class BitmessageContext private constructor(builder: BitmessageContext.Builder) | |||||||
|             return this |             return this | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         @JvmName("kotlinListener") | ||||||
|         fun listener(listener: (Plaintext) -> Unit): Builder { |         fun listener(listener: (Plaintext) -> Unit): Builder { | ||||||
|             this.listener = object : Listener { |             this.listener = object : Listener { | ||||||
|                 override fun receive(plaintext: Plaintext) { |                 override fun receive(plaintext: Plaintext) { | ||||||
| @@ -428,6 +447,10 @@ class BitmessageContext private constructor(builder: BitmessageContext.Builder) | |||||||
|             return this |             return this | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         fun doMissingProofOfWorkDelay(seconds: Int) { | ||||||
|  |             this.doMissingProofOfWorkDelay = seconds | ||||||
|  |         } | ||||||
|  |  | ||||||
|         /** |         /** | ||||||
|          * By default a client will send the public key when an identity is being created. On weaker devices |          * By default a client will send the public key when an identity is being created. On weaker devices | ||||||
|          * this behaviour might not be desirable. |          * this behaviour might not be desirable. | ||||||
| @@ -438,18 +461,34 @@ class BitmessageContext private constructor(builder: BitmessageContext.Builder) | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         fun build(): BitmessageContext { |         fun build(): BitmessageContext { | ||||||
|             nonNull("inventory", inventory) |  | ||||||
|             nonNull("nodeRegistry", nodeRegistry) |  | ||||||
|             nonNull("networkHandler", networkHandler) |  | ||||||
|             nonNull("addressRepo", addressRepo) |  | ||||||
|             nonNull("messageRepo", messageRepo) |  | ||||||
|             nonNull("proofOfWorkRepo", proofOfWorkRepository) |  | ||||||
|             return BitmessageContext(this) |             return BitmessageContext(this) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         private fun nonNull(name: String, o: Any?) { |  | ||||||
|             if (o == null) throw IllegalStateException(name + " must not be null") |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     init { | ||||||
|  |         this.labeler = labeler | ||||||
|  |         this.internals = InternalContext( | ||||||
|  |             cryptography, | ||||||
|  |             inventory, | ||||||
|  |             nodeRegistry, | ||||||
|  |             networkHandler, | ||||||
|  |             addressRepository, | ||||||
|  |             messageRepository, | ||||||
|  |             proofOfWorkRepository, | ||||||
|  |             proofOfWorkEngine, | ||||||
|  |             customCommandHandler, | ||||||
|  |             listener, | ||||||
|  |             labeler, | ||||||
|  |             port, | ||||||
|  |             connectionTTL, | ||||||
|  |             connectionLimit | ||||||
|  |         ) | ||||||
|  |         this.addresses = addressRepository | ||||||
|  |         this.messages = messageRepository | ||||||
|  |         this.sendPubkeyOnIdentityCreation = sendPubkeyOnIdentityCreation | ||||||
|  |         (listener as? Listener.WithContext)?.setContext(this) | ||||||
|  |         internals.proofOfWorkService.doMissingProofOfWork(doMissingProofOfWorkDelayInSeconds * 1000L) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     companion object { |     companion object { | ||||||
|   | |||||||
| @@ -25,6 +25,7 @@ import ch.dissem.bitmessage.entity.valueobject.InventoryVector | |||||||
| import ch.dissem.bitmessage.exception.DecryptionFailedException | import ch.dissem.bitmessage.exception.DecryptionFailedException | ||||||
| import ch.dissem.bitmessage.ports.Labeler | import ch.dissem.bitmessage.ports.Labeler | ||||||
| import ch.dissem.bitmessage.ports.NetworkHandler | import ch.dissem.bitmessage.ports.NetworkHandler | ||||||
|  | import ch.dissem.bitmessage.utils.Strings.hex | ||||||
| import org.slf4j.LoggerFactory | import org.slf4j.LoggerFactory | ||||||
| import java.util.* | import java.util.* | ||||||
|  |  | ||||||
| @@ -32,23 +33,23 @@ internal open class DefaultMessageListener( | |||||||
|     private val labeler: Labeler, |     private val labeler: Labeler, | ||||||
|     private val listener: BitmessageContext.Listener |     private val listener: BitmessageContext.Listener | ||||||
| ) : NetworkHandler.MessageListener { | ) : NetworkHandler.MessageListener { | ||||||
|     private var ctx by InternalContext |     private var ctx by InternalContext.lateinit | ||||||
|  |  | ||||||
|     override fun receive(`object`: ObjectMessage) { |     override fun receive(objectMessage: ObjectMessage) { | ||||||
|         val payload = `object`.payload |         val payload = objectMessage.payload | ||||||
|  |  | ||||||
|         when (payload.type) { |         when (payload.type) { | ||||||
|             ObjectType.GET_PUBKEY -> { |             ObjectType.GET_PUBKEY -> { | ||||||
|                 receive(`object`, payload as GetPubkey) |                 receive(objectMessage, payload as GetPubkey) | ||||||
|             } |             } | ||||||
|             ObjectType.PUBKEY -> { |             ObjectType.PUBKEY -> { | ||||||
|                 receive(`object`, payload as Pubkey) |                 receive(objectMessage, payload as Pubkey) | ||||||
|             } |             } | ||||||
|             ObjectType.MSG -> { |             ObjectType.MSG -> { | ||||||
|                 receive(`object`, payload as Msg) |                 receive(objectMessage, payload as Msg) | ||||||
|             } |             } | ||||||
|             ObjectType.BROADCAST -> { |             ObjectType.BROADCAST -> { | ||||||
|                 receive(`object`, payload as Broadcast) |                 receive(objectMessage, payload as Broadcast) | ||||||
|             } |             } | ||||||
|             null -> { |             null -> { | ||||||
|                 if (payload is GenericPayload) { |                 if (payload is GenericPayload) { | ||||||
| @@ -61,30 +62,33 @@ internal open class DefaultMessageListener( | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected fun receive(`object`: ObjectMessage, getPubkey: GetPubkey) { |     protected fun receive(objectMessage: ObjectMessage, getPubkey: GetPubkey) { | ||||||
|         val identity = ctx.addressRepository.findIdentity(getPubkey.ripeTag) |         val identity = ctx.addressRepository.findIdentity(getPubkey.ripeTag) | ||||||
|         if (identity != null && identity.privateKey != null && !identity.isChan) { |         if (identity != null && identity.privateKey != null && !identity.isChan) { | ||||||
|             LOG.info("Got pubkey request for identity " + identity) |             LOG.info("Got pubkey request for identity " + identity) | ||||||
|             // FIXME: only send pubkey if it wasn't sent in the last TTL.pubkey() days |             // FIXME: only send pubkey if it wasn't sent in the last TTL.pubkey() days | ||||||
|             ctx.sendPubkey(identity, `object`.stream) |             ctx.sendPubkey(identity, objectMessage.stream) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected fun receive(`object`: ObjectMessage, pubkey: Pubkey) { |     protected fun receive(objectMessage: ObjectMessage, pubkey: Pubkey) { | ||||||
|         val address: BitmessageAddress? |  | ||||||
|         try { |         try { | ||||||
|             if (pubkey is V4Pubkey) { |             if (pubkey is V4Pubkey) { | ||||||
|                 address = ctx.addressRepository.findContact(pubkey.tag) |                 ctx.addressRepository.findContact(pubkey.tag)?.let { | ||||||
|                 if (address != null) { |                     if (it.pubkey == null) { | ||||||
|                     pubkey.decrypt(address.publicDecryptionKey) |                         pubkey.decrypt(it.publicDecryptionKey) | ||||||
|  |                         updatePubkey(it, pubkey) | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|             } else { |             } else { | ||||||
|                 address = ctx.addressRepository.findContact(pubkey.ripe) |                 ctx.addressRepository.findContact(pubkey.ripe)?.let { | ||||||
|  |                     if (it.pubkey == null) { | ||||||
|  |                         updatePubkey(it, pubkey) | ||||||
|                     } |                     } | ||||||
|             if (address != null && address.pubkey == null) { |  | ||||||
|                 updatePubkey(address, pubkey) |  | ||||||
|                 } |                 } | ||||||
|         } catch (_: DecryptionFailedException) {} |             } | ||||||
|  |         } catch (_: DecryptionFailedException) { | ||||||
|  |         } | ||||||
|  |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -101,19 +105,20 @@ internal open class DefaultMessageListener( | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected fun receive(`object`: ObjectMessage, msg: Msg) { |     protected fun receive(objectMessage: ObjectMessage, msg: Msg) { | ||||||
|         for (identity in ctx.addressRepository.getIdentities()) { |         for (identity in ctx.addressRepository.getIdentities()) { | ||||||
|             try { |             try { | ||||||
|                 msg.decrypt(identity.privateKey!!.privateEncryptionKey) |                 msg.decrypt(identity.privateKey!!.privateEncryptionKey) | ||||||
|                 val plaintext = msg.plaintext!! |                 val plaintext = msg.plaintext!! | ||||||
|                 plaintext.to = identity |                 plaintext.to = identity | ||||||
|                 if (!`object`.isSignatureValid(plaintext.from.pubkey!!)) { |                 if (!objectMessage.isSignatureValid(plaintext.from.pubkey!!)) { | ||||||
|                     LOG.warn("Msg with IV " + `object`.inventoryVector + " was successfully decrypted, but signature check failed. Ignoring.") |                     LOG.warn("Msg with IV " + objectMessage.inventoryVector + " was successfully decrypted, but signature check failed. Ignoring.") | ||||||
|                 } else { |                 } else { | ||||||
|                     receive(`object`.inventoryVector, plaintext) |                     receive(objectMessage.inventoryVector, plaintext) | ||||||
|                 } |                 } | ||||||
|                 break |                 break | ||||||
|             } catch (_: DecryptionFailedException) {} |             } catch (_: DecryptionFailedException) { | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -122,11 +127,11 @@ internal open class DefaultMessageListener( | |||||||
|             ctx.messageRepository.getMessageForAck(ack.data)?.let { |             ctx.messageRepository.getMessageForAck(ack.data)?.let { | ||||||
|                 ctx.labeler.markAsAcknowledged(it) |                 ctx.labeler.markAsAcknowledged(it) | ||||||
|                 ctx.messageRepository.save(it) |                 ctx.messageRepository.save(it) | ||||||
|             } |             } ?: LOG.debug("Message not found for ack ${hex(ack.data)}") | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected fun receive(`object`: ObjectMessage, broadcast: Broadcast) { |     protected fun receive(objectMessage: ObjectMessage, broadcast: Broadcast) { | ||||||
|         val tag = if (broadcast is V5Broadcast) broadcast.tag else null |         val tag = if (broadcast is V5Broadcast) broadcast.tag else null | ||||||
|         for (subscription in ctx.addressRepository.getSubscriptions(broadcast.version)) { |         for (subscription in ctx.addressRepository.getSubscriptions(broadcast.version)) { | ||||||
|             if (tag != null && !Arrays.equals(tag, subscription.tag)) { |             if (tag != null && !Arrays.equals(tag, subscription.tag)) { | ||||||
| @@ -134,12 +139,13 @@ internal open class DefaultMessageListener( | |||||||
|             } |             } | ||||||
|             try { |             try { | ||||||
|                 broadcast.decrypt(subscription.publicDecryptionKey) |                 broadcast.decrypt(subscription.publicDecryptionKey) | ||||||
|                 if (!`object`.isSignatureValid(broadcast.plaintext!!.from.pubkey!!)) { |                 if (!objectMessage.isSignatureValid(broadcast.plaintext!!.from.pubkey!!)) { | ||||||
|                     LOG.warn("Broadcast with IV " + `object`.inventoryVector + " was successfully decrypted, but signature check failed. Ignoring.") |                     LOG.warn("Broadcast with IV " + objectMessage.inventoryVector + " was successfully decrypted, but signature check failed. Ignoring.") | ||||||
|                 } else { |                 } else { | ||||||
|                     receive(`object`.inventoryVector, broadcast.plaintext!!) |                     receive(objectMessage.inventoryVector, broadcast.plaintext!!) | ||||||
|  |                 } | ||||||
|  |             } catch (_: DecryptionFailedException) { | ||||||
|             } |             } | ||||||
|             } catch (_: DecryptionFailedException) {} |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -158,7 +164,7 @@ internal open class DefaultMessageListener( | |||||||
|             msg.ackMessage?.let { |             msg.ackMessage?.let { | ||||||
|                 ctx.inventory.storeObject(it) |                 ctx.inventory.storeObject(it) | ||||||
|                 ctx.networkHandler.offer(it.inventoryVector) |                 ctx.networkHandler.offer(it.inventoryVector) | ||||||
|             } |             } ?: LOG.debug("ack message expected") | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -28,7 +28,6 @@ import ch.dissem.bitmessage.utils.UnixTime | |||||||
| import org.slf4j.LoggerFactory | import org.slf4j.LoggerFactory | ||||||
| import java.util.* | import java.util.* | ||||||
| import java.util.concurrent.Executors | import java.util.concurrent.Executors | ||||||
| import kotlin.properties.Delegates |  | ||||||
| import kotlin.reflect.KProperty | import kotlin.reflect.KProperty | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -68,7 +67,8 @@ class InternalContext( | |||||||
|         get() = _streams.toLongArray() |         get() = _streams.toLongArray() | ||||||
|  |  | ||||||
|     init { |     init { | ||||||
|         instance = this |         lateinit.instance = this | ||||||
|  |         lateinit = ContextDelegate() | ||||||
|         Singleton.initialize(cryptography) |         Singleton.initialize(cryptography) | ||||||
|  |  | ||||||
|         // TODO: streams of new identities and subscriptions should also be added. This works only after a restart. |         // TODO: streams of new identities and subscriptions should also be added. This works only after a restart. | ||||||
| @@ -102,24 +102,24 @@ class InternalContext( | |||||||
|         val recipient = to ?: from |         val recipient = to ?: from | ||||||
|         val expires = UnixTime.now + timeToLive |         val expires = UnixTime.now + timeToLive | ||||||
|         LOG.info("Expires at " + expires) |         LOG.info("Expires at " + expires) | ||||||
|         val `object` = ObjectMessage( |         val objectMessage = ObjectMessage( | ||||||
|             stream = recipient.stream, |             stream = recipient.stream, | ||||||
|             expiresTime = expires, |             expiresTime = expires, | ||||||
|             payload = payload |             payload = payload | ||||||
|         ) |         ) | ||||||
|         if (`object`.isSigned) { |         if (objectMessage.isSigned) { | ||||||
|             `object`.sign( |             objectMessage.sign( | ||||||
|                 from.privateKey ?: throw IllegalArgumentException("The given sending address is no identity") |                 from.privateKey ?: throw IllegalArgumentException("The given sending address is no identity") | ||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
|         if (payload is Broadcast) { |         if (payload is Broadcast) { | ||||||
|             payload.encrypt() |             payload.encrypt() | ||||||
|         } else if (payload is Encrypted) { |         } else if (payload is Encrypted) { | ||||||
|             `object`.encrypt( |             objectMessage.encrypt( | ||||||
|                 recipient.pubkey ?: throw IllegalArgumentException("The public key for the recipient isn't available") |                 recipient.pubkey ?: throw IllegalArgumentException("The public key for the recipient isn't available") | ||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
|         proofOfWorkService.doProofOfWork(to, `object`) |         proofOfWorkService.doProofOfWork(to, objectMessage) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun sendPubkey(identity: BitmessageAddress, targetStream: Long) { |     fun sendPubkey(identity: BitmessageAddress, targetStream: Long) { | ||||||
| @@ -163,12 +163,11 @@ class InternalContext( | |||||||
|             } |             } | ||||||
|  |  | ||||||
|             val expires = UnixTime.now + TTL.getpubkey |             val expires = UnixTime.now + TTL.getpubkey | ||||||
|             LOG.info("Expires at " + expires) |             LOG.info("Expires at $expires") | ||||||
|             val payload = GetPubkey(contact) |  | ||||||
|             val request = ObjectMessage( |             val request = ObjectMessage( | ||||||
|                 stream = contact.stream, |                 stream = contact.stream, | ||||||
|                 expiresTime = expires, |                 expiresTime = expires, | ||||||
|                 payload = payload |                 payload = GetPubkey(contact) | ||||||
|             ) |             ) | ||||||
|             proofOfWorkService.doProofOfWork(request) |             proofOfWorkService.doProofOfWork(request) | ||||||
|         } |         } | ||||||
| @@ -179,14 +178,14 @@ class InternalContext( | |||||||
|             address.alias = it.alias |             address.alias = it.alias | ||||||
|             address.isSubscribed = it.isSubscribed |             address.isSubscribed = it.isSubscribed | ||||||
|         } |         } | ||||||
|         for (`object` in inventory.getObjects(address.stream, address.version, ObjectType.PUBKEY)) { |         for (objectMessage in inventory.getObjects(address.stream, address.version, ObjectType.PUBKEY)) { | ||||||
|             try { |             try { | ||||||
|                 val pubkey = `object`.payload as Pubkey |                 val pubkey = objectMessage.payload as Pubkey | ||||||
|                 if (address.version == 4L) { |                 if (address.version == 4L) { | ||||||
|                     val v4Pubkey = pubkey as V4Pubkey |                     val v4Pubkey = pubkey as V4Pubkey | ||||||
|                     if (Arrays.equals(address.tag, v4Pubkey.tag)) { |                     if (Arrays.equals(address.tag, v4Pubkey.tag)) { | ||||||
|                         v4Pubkey.decrypt(address.publicDecryptionKey) |                         v4Pubkey.decrypt(address.publicDecryptionKey) | ||||||
|                         if (`object`.isSignatureValid(v4Pubkey)) { |                         if (objectMessage.isSignatureValid(v4Pubkey)) { | ||||||
|                             address.pubkey = v4Pubkey |                             address.pubkey = v4Pubkey | ||||||
|                             addressRepository.save(address) |                             addressRepository.save(address) | ||||||
|                             break |                             break | ||||||
| @@ -219,17 +218,19 @@ class InternalContext( | |||||||
|         fun setContext(context: InternalContext) |         fun setContext(context: InternalContext) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     class ContextDelegate { | ||||||
|  |         internal lateinit var instance: InternalContext | ||||||
|  |         operator fun getValue(thisRef: Any?, property: KProperty<*>) = instance | ||||||
|  |         operator fun setValue(thisRef: Any?, property: KProperty<*>, value: InternalContext) {} | ||||||
|  |     } | ||||||
|  |  | ||||||
|     companion object { |     companion object { | ||||||
|         private val LOG = LoggerFactory.getLogger(InternalContext::class.java) |         private val LOG = LoggerFactory.getLogger(InternalContext::class.java) | ||||||
|  |  | ||||||
|         @JvmField val NETWORK_NONCE_TRIALS_PER_BYTE: Long = 1000 |         @JvmField val NETWORK_NONCE_TRIALS_PER_BYTE: Long = 1000 | ||||||
|         @JvmField val NETWORK_EXTRA_BYTES: Long = 1000 |         @JvmField val NETWORK_EXTRA_BYTES: Long = 1000 | ||||||
|  |  | ||||||
|         private var instance: InternalContext by Delegates.notNull<InternalContext>() |         var lateinit = ContextDelegate() | ||||||
|  |             private set | ||||||
|         operator fun getValue(thisRef: Any?, property: KProperty<*>) = instance |  | ||||||
|         operator fun setValue(thisRef: Any?, property: KProperty<*>, value: InternalContext) { |  | ||||||
|             instance = value |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -22,6 +22,7 @@ import ch.dissem.bitmessage.entity.* | |||||||
| import ch.dissem.bitmessage.entity.payload.Msg | import ch.dissem.bitmessage.entity.payload.Msg | ||||||
| import ch.dissem.bitmessage.ports.ProofOfWorkEngine | import ch.dissem.bitmessage.ports.ProofOfWorkEngine | ||||||
| import ch.dissem.bitmessage.ports.ProofOfWorkRepository.Item | import ch.dissem.bitmessage.ports.ProofOfWorkRepository.Item | ||||||
|  | import ch.dissem.bitmessage.utils.Strings | ||||||
| import org.slf4j.LoggerFactory | import org.slf4j.LoggerFactory | ||||||
| import java.io.IOException | import java.io.IOException | ||||||
| import java.util.* | import java.util.* | ||||||
| @@ -31,7 +32,7 @@ import java.util.* | |||||||
|  */ |  */ | ||||||
| class ProofOfWorkService : ProofOfWorkEngine.Callback { | class ProofOfWorkService : ProofOfWorkEngine.Callback { | ||||||
|  |  | ||||||
|     private val ctx by InternalContext |     private val ctx by InternalContext.lateinit | ||||||
|     private val cryptography by lazy { ctx.cryptography } |     private val cryptography by lazy { ctx.cryptography } | ||||||
|     private val powRepo by lazy { ctx.proofOfWorkRepository } |     private val powRepo by lazy { ctx.proofOfWorkRepository } | ||||||
|     private val messageRepo by lazy { ctx.messageRepository } |     private val messageRepo by lazy { ctx.messageRepository } | ||||||
| @@ -45,75 +46,69 @@ class ProofOfWorkService : ProofOfWorkEngine.Callback { | |||||||
|             override fun run() { |             override fun run() { | ||||||
|                 LOG.info("Doing POW for " + items.size + " tasks.") |                 LOG.info("Doing POW for " + items.size + " tasks.") | ||||||
|                 for (initialHash in items) { |                 for (initialHash in items) { | ||||||
|                     val (`object`, nonceTrialsPerByte, extraBytes) = powRepo.getItem(initialHash) |                     val (objectMessage, nonceTrialsPerByte, extraBytes) = powRepo.getItem(initialHash) | ||||||
|                     cryptography.doProofOfWork(`object`, nonceTrialsPerByte, extraBytes, |                     cryptography.doProofOfWork(objectMessage, nonceTrialsPerByte, extraBytes, | ||||||
|                         this@ProofOfWorkService) |                         this@ProofOfWorkService) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }, delayInMilliseconds) |         }, delayInMilliseconds) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun doProofOfWork(`object`: ObjectMessage) { |     fun doProofOfWork(objectMessage: ObjectMessage) { | ||||||
|         doProofOfWork(null, `object`) |         doProofOfWork(null, objectMessage) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun doProofOfWork(recipient: BitmessageAddress?, `object`: ObjectMessage) { |     fun doProofOfWork(recipient: BitmessageAddress?, objectMessage: ObjectMessage) { | ||||||
|         val pubkey = recipient?.pubkey |         val pubkey = recipient?.pubkey | ||||||
|  |  | ||||||
|         val nonceTrialsPerByte = pubkey?.nonceTrialsPerByte ?: NETWORK_NONCE_TRIALS_PER_BYTE |         val nonceTrialsPerByte = pubkey?.nonceTrialsPerByte ?: NETWORK_NONCE_TRIALS_PER_BYTE | ||||||
|         val extraBytes = pubkey?.extraBytes ?: NETWORK_EXTRA_BYTES |         val extraBytes = pubkey?.extraBytes ?: NETWORK_EXTRA_BYTES | ||||||
|  |  | ||||||
|         powRepo.putObject(`object`, nonceTrialsPerByte, extraBytes) |         powRepo.putObject(objectMessage, nonceTrialsPerByte, extraBytes) | ||||||
|         if (`object`.payload is PlaintextHolder) { |         if (objectMessage.payload is PlaintextHolder) { | ||||||
|             `object`.payload.plaintext?.let { |             objectMessage.payload.plaintext?.let { | ||||||
|                 it.initialHash = cryptography.getInitialHash(`object`) |                 it.initialHash = cryptography.getInitialHash(objectMessage) | ||||||
|                 messageRepo.save(it) |                 messageRepo.save(it) | ||||||
|  |             } ?: LOG.error("PlaintextHolder without Plaintext shouldn't make it to the POW") | ||||||
|         } |         } | ||||||
|         } |         cryptography.doProofOfWork(objectMessage, nonceTrialsPerByte, extraBytes, this) | ||||||
|         cryptography.doProofOfWork(`object`, nonceTrialsPerByte, extraBytes, this) |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun doProofOfWorkWithAck(plaintext: Plaintext, expirationTime: Long) { |     fun doProofOfWorkWithAck(plaintext: Plaintext, expirationTime: Long) { | ||||||
|         val ack = plaintext.ackMessage |         val ack = plaintext.ackMessage!! | ||||||
|         messageRepo.save(plaintext) |         messageRepo.save(plaintext) | ||||||
|         val item = Item(ack!!, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES, |         val item = Item(ack, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES, | ||||||
|             expirationTime, plaintext) |             expirationTime, plaintext) | ||||||
|         powRepo.putObject(item) |         powRepo.putObject(item) | ||||||
|         cryptography.doProofOfWork(ack, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES, this) |         cryptography.doProofOfWork(ack, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES, this) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) { |     override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) { | ||||||
|         val (`object`, _, _, expirationTime, message) = powRepo.getItem(initialHash) |         val (objectMessage, _, _, expirationTime, message) = powRepo.getItem(initialHash) | ||||||
|         if (message == null) { |         if (message == null) { | ||||||
|             `object`.nonce = nonce |             objectMessage.nonce = nonce | ||||||
|             messageRepo.getMessage(initialHash)?.let { |             messageRepo.getMessage(initialHash)?.let { | ||||||
|                 it.inventoryVector = `object`.inventoryVector |                 it.inventoryVector = objectMessage.inventoryVector | ||||||
|                 it.updateNextTry() |                 it.updateNextTry() | ||||||
|                 ctx.labeler.markAsSent(it) |                 ctx.labeler.markAsSent(it) | ||||||
|                 messageRepo.save(it) |                 messageRepo.save(it) | ||||||
|             } |             } | ||||||
|             try { |             ctx.inventory.storeObject(objectMessage) | ||||||
|                 ctx.networkListener.receive(`object`) |             ctx.networkHandler.offer(objectMessage.inventoryVector) | ||||||
|             } catch (e: IOException) { |  | ||||||
|                 LOG.debug(e.message, e) |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             ctx.inventory.storeObject(`object`) |  | ||||||
|             ctx.networkHandler.offer(`object`.inventoryVector) |  | ||||||
|         } else { |         } else { | ||||||
|             message.ackMessage!!.nonce = nonce |             message.ackMessage!!.nonce = nonce | ||||||
|             val `object` = ObjectMessage.Builder() |             val newObjectMessage = ObjectMessage.Builder() | ||||||
|                 .stream(message.stream) |                 .stream(message.stream) | ||||||
|                 .expiresTime(expirationTime!!) |                 .expiresTime(expirationTime!!) | ||||||
|                 .payload(Msg(message)) |                 .payload(Msg(message)) | ||||||
|                 .build() |                 .build() | ||||||
|             if (`object`.isSigned) { |             if (newObjectMessage.isSigned) { | ||||||
|                 `object`.sign(message.from.privateKey!!) |                 newObjectMessage.sign(message.from.privateKey!!) | ||||||
|             } |             } | ||||||
|             if (`object`.payload is Encrypted) { |             if (newObjectMessage.payload is Encrypted) { | ||||||
|                 `object`.encrypt(message.to!!.pubkey!!) |                 newObjectMessage.encrypt(message.to!!.pubkey!!) | ||||||
|             } |             } | ||||||
|             doProofOfWork(message.to, `object`) |             doProofOfWork(message.to, newObjectMessage) | ||||||
|         } |         } | ||||||
|         powRepo.removeObject(initialHash) |         powRepo.removeObject(initialHash) | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -28,14 +28,14 @@ data class Addr constructor(val addresses: List<NetworkAddress>) : MessagePayloa | |||||||
|     override val command: MessagePayload.Command = MessagePayload.Command.ADDR |     override val command: MessagePayload.Command = MessagePayload.Command.ADDR | ||||||
|  |  | ||||||
|     override fun write(out: OutputStream) { |     override fun write(out: OutputStream) { | ||||||
|         Encode.varInt(addresses.size.toLong(), out) |         Encode.varInt(addresses.size, out) | ||||||
|         for (address in addresses) { |         for (address in addresses) { | ||||||
|             address.write(out) |             address.write(out) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun write(buffer: ByteBuffer) { |     override fun write(buffer: ByteBuffer) { | ||||||
|         Encode.varInt(addresses.size.toLong(), buffer) |         Encode.varInt(addresses.size, buffer) | ||||||
|         for (address in addresses) { |         for (address in addresses) { | ||||||
|             address.write(buffer) |             address.write(buffer) | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -29,14 +29,14 @@ class GetData constructor(var inventory: List<InventoryVector>) : MessagePayload | |||||||
|     override val command: MessagePayload.Command = MessagePayload.Command.GETDATA |     override val command: MessagePayload.Command = MessagePayload.Command.GETDATA | ||||||
|  |  | ||||||
|     override fun write(out: OutputStream) { |     override fun write(out: OutputStream) { | ||||||
|         Encode.varInt(inventory.size.toLong(), out) |         Encode.varInt(inventory.size, out) | ||||||
|         for (iv in inventory) { |         for (iv in inventory) { | ||||||
|             iv.write(out) |             iv.write(out) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun write(buffer: ByteBuffer) { |     override fun write(buffer: ByteBuffer) { | ||||||
|         Encode.varInt(inventory.size.toLong(), buffer) |         Encode.varInt(inventory.size, buffer) | ||||||
|         for (iv in inventory) { |         for (iv in inventory) { | ||||||
|             iv.write(buffer) |             iv.write(buffer) | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -29,14 +29,14 @@ class Inv constructor(val inventory: List<InventoryVector>) : MessagePayload { | |||||||
|     override val command: MessagePayload.Command = MessagePayload.Command.INV |     override val command: MessagePayload.Command = MessagePayload.Command.INV | ||||||
|  |  | ||||||
|     override fun write(out: OutputStream) { |     override fun write(out: OutputStream) { | ||||||
|         Encode.varInt(inventory.size.toLong(), out) |         Encode.varInt(inventory.size, out) | ||||||
|         for (iv in inventory) { |         for (iv in inventory) { | ||||||
|             iv.write(out) |             iv.write(out) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun write(buffer: ByteBuffer) { |     override fun write(buffer: ByteBuffer) { | ||||||
|         Encode.varInt(inventory.size.toLong(), buffer) |         Encode.varInt(inventory.size, buffer) | ||||||
|         for (iv in inventory) { |         for (iv in inventory) { | ||||||
|             iv.write(buffer) |             iv.write(buffer) | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -43,7 +43,7 @@ data class NetworkMessage( | |||||||
|     @Throws(IOException::class) |     @Throws(IOException::class) | ||||||
|     override fun write(out: OutputStream) { |     override fun write(out: OutputStream) { | ||||||
|         // magic |         // magic | ||||||
|         Encode.int32(MAGIC.toLong(), out) |         Encode.int32(MAGIC, out) | ||||||
|  |  | ||||||
|         // ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected) |         // ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected) | ||||||
|         val command = payload.command.name.toLowerCase() |         val command = payload.command.name.toLowerCase() | ||||||
| @@ -57,7 +57,7 @@ data class NetworkMessage( | |||||||
|         // Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would |         // Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would | ||||||
|         // ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are |         // ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are | ||||||
|         // larger than this. |         // larger than this. | ||||||
|         Encode.int32(payloadBytes.size.toLong(), out) |         Encode.int32(payloadBytes.size, out) | ||||||
|  |  | ||||||
|         // checksum |         // checksum | ||||||
|         out.write(getChecksum(payloadBytes)) |         out.write(getChecksum(payloadBytes)) | ||||||
| @@ -92,7 +92,7 @@ data class NetworkMessage( | |||||||
|  |  | ||||||
|     private fun writeHeader(out: ByteBuffer): ByteArray { |     private fun writeHeader(out: ByteBuffer): ByteArray { | ||||||
|         // magic |         // magic | ||||||
|         Encode.int32(MAGIC.toLong(), out) |         Encode.int32(MAGIC, out) | ||||||
|  |  | ||||||
|         // ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected) |         // ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected) | ||||||
|         val command = payload.command.name.toLowerCase() |         val command = payload.command.name.toLowerCase() | ||||||
| @@ -107,7 +107,7 @@ data class NetworkMessage( | |||||||
|         // Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would |         // Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would | ||||||
|         // ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are |         // ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are | ||||||
|         // larger than this. |         // larger than this. | ||||||
|         Encode.int32(payloadBytes.size.toLong(), out) |         Encode.int32(payloadBytes.size, out) | ||||||
|  |  | ||||||
|         // checksum |         // checksum | ||||||
|         out.put(getChecksum(payloadBytes)) |         out.put(getChecksum(payloadBytes)) | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ | |||||||
| package ch.dissem.bitmessage.entity | package ch.dissem.bitmessage.entity | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.entity.Plaintext.Encoding.* | import ch.dissem.bitmessage.entity.Plaintext.Encoding.* | ||||||
|  | import ch.dissem.bitmessage.entity.Plaintext.Type.MSG | ||||||
| import ch.dissem.bitmessage.entity.payload.Msg | import ch.dissem.bitmessage.entity.payload.Msg | ||||||
| import ch.dissem.bitmessage.entity.payload.Pubkey.Feature | import ch.dissem.bitmessage.entity.payload.Pubkey.Feature | ||||||
| import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding | import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding | ||||||
| @@ -38,10 +39,20 @@ import kotlin.collections.HashSet | |||||||
| fun message(encoding: Plaintext.Encoding, subject: String, body: String): ByteArray = when (encoding) { | fun message(encoding: Plaintext.Encoding, subject: String, body: String): ByteArray = when (encoding) { | ||||||
|     SIMPLE -> "Subject:$subject\nBody:$body".toByteArray() |     SIMPLE -> "Subject:$subject\nBody:$body".toByteArray() | ||||||
|     EXTENDED -> Message.Builder().subject(subject).body(body).build().zip() |     EXTENDED -> Message.Builder().subject(subject).body(body).build().zip() | ||||||
|     TRIVIAL -> (subject+body).toByteArray() |     TRIVIAL -> (subject + body).toByteArray() | ||||||
|     IGNORE -> ByteArray(0) |     IGNORE -> ByteArray(0) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | fun ackData(type: Plaintext.Type, ackData: ByteArray?): ByteArray? { | ||||||
|  |     if (ackData != null) { | ||||||
|  |         return ackData | ||||||
|  |     } else if (type == MSG) { | ||||||
|  |         return cryptography().randomBytes(Msg.ACK_LENGTH) | ||||||
|  |     } else { | ||||||
|  |         return null | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * The unencrypted message to be sent by 'msg' or 'broadcast'. |  * The unencrypted message to be sent by 'msg' or 'broadcast'. | ||||||
|  */ |  */ | ||||||
| @@ -52,7 +63,7 @@ class Plaintext private constructor( | |||||||
|     val encodingCode: Long, |     val encodingCode: Long, | ||||||
|     val message: ByteArray, |     val message: ByteArray, | ||||||
|     val ackData: ByteArray?, |     val ackData: ByteArray?, | ||||||
|     ackMessage: Lazy<ObjectMessage?>, |     ackMessage: Lazy<ObjectMessage?> = lazy { Factory.createAck(from, ackData, ttl) }, | ||||||
|     val conversationId: UUID = UUID.randomUUID(), |     val conversationId: UUID = UUID.randomUUID(), | ||||||
|     var inventoryVector: InventoryVector? = null, |     var inventoryVector: InventoryVector? = null, | ||||||
|     var signature: ByteArray? = null, |     var signature: ByteArray? = null, | ||||||
| @@ -121,7 +132,7 @@ class Plaintext private constructor( | |||||||
|         to: BitmessageAddress?, |         to: BitmessageAddress?, | ||||||
|         encoding: Encoding, |         encoding: Encoding, | ||||||
|         message: ByteArray, |         message: ByteArray, | ||||||
|         ackData: ByteArray = cryptography().randomBytes(Msg.ACK_LENGTH), |         ackData: ByteArray? = null, | ||||||
|         conversationId: UUID = UUID.randomUUID(), |         conversationId: UUID = UUID.randomUUID(), | ||||||
|         inventoryVector: InventoryVector? = null, |         inventoryVector: InventoryVector? = null, | ||||||
|         signature: ByteArray? = null, |         signature: ByteArray? = null, | ||||||
| @@ -131,21 +142,20 @@ class Plaintext private constructor( | |||||||
|         labels: MutableSet<Label> = HashSet(), |         labels: MutableSet<Label> = HashSet(), | ||||||
|         status: Status |         status: Status | ||||||
|     ) : this( |     ) : this( | ||||||
|         type, |         type = type, | ||||||
|         from, |         from = from, | ||||||
|         to, |         to = to, | ||||||
|         encoding.code, |         encoding = encoding.code, | ||||||
|         message, |         message = message, | ||||||
|         ackData, |         ackMessage = ackData(type, ackData), | ||||||
|         lazy { Factory.createAck(from, ackData, ttl) }, |         conversationId = conversationId, | ||||||
|         conversationId, |         inventoryVector = inventoryVector, | ||||||
|         inventoryVector, |         signature = signature, | ||||||
|         signature, |         received = received, | ||||||
|         received, |         initialHash = initialHash, | ||||||
|         initialHash, |         ttl = ttl, | ||||||
|         ttl, |         labels = labels, | ||||||
|         labels, |         status = status | ||||||
|         status |  | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     constructor( |     constructor( | ||||||
| @@ -164,13 +174,13 @@ class Plaintext private constructor( | |||||||
|         labels: MutableSet<Label> = HashSet(), |         labels: MutableSet<Label> = HashSet(), | ||||||
|         status: Status |         status: Status | ||||||
|     ) : this( |     ) : this( | ||||||
|         type, |         type = type, | ||||||
|         from, |         from = from, | ||||||
|         to, |         to = to, | ||||||
|         encoding, |         encodingCode = encoding, | ||||||
|         message, |         message = message, | ||||||
|         null, |         ackData = null, | ||||||
|         lazy { |         ackMessage = lazy { | ||||||
|             if (ackMessage != null && ackMessage.isNotEmpty()) { |             if (ackMessage != null && ackMessage.isNotEmpty()) { | ||||||
|                 Factory.getObjectMessage( |                 Factory.getObjectMessage( | ||||||
|                     3, |                     3, | ||||||
| @@ -178,14 +188,14 @@ class Plaintext private constructor( | |||||||
|                     ackMessage.size) |                     ackMessage.size) | ||||||
|             } else null |             } else null | ||||||
|         }, |         }, | ||||||
|         conversationId, |         conversationId = conversationId, | ||||||
|         inventoryVector, |         inventoryVector = inventoryVector, | ||||||
|         signature, |         signature = signature, | ||||||
|         received, |         received = received, | ||||||
|         initialHash, |         initialHash = initialHash, | ||||||
|         ttl, |         ttl = ttl, | ||||||
|         labels, |         labels = labels, | ||||||
|         status |         status = status | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     constructor( |     constructor( | ||||||
| @@ -195,37 +205,36 @@ class Plaintext private constructor( | |||||||
|         encoding: Encoding = SIMPLE, |         encoding: Encoding = SIMPLE, | ||||||
|         subject: String, |         subject: String, | ||||||
|         body: String, |         body: String, | ||||||
|         ackData: ByteArray = cryptography().randomBytes(Msg.ACK_LENGTH), |         ackData: ByteArray? = null, | ||||||
|         conversationId: UUID = UUID.randomUUID(), |         conversationId: UUID = UUID.randomUUID(), | ||||||
|         ttl: Long = TTL.msg, |         ttl: Long = TTL.msg, | ||||||
|         labels: MutableSet<Label> = HashSet(), |         labels: MutableSet<Label> = HashSet(), | ||||||
|         status: Status = Status.DRAFT |         status: Status = Status.DRAFT | ||||||
|     ) : this( |     ) : this( | ||||||
|         type, |         type = type, | ||||||
|         from, |         from = from, | ||||||
|         to, |         to = to, | ||||||
|         encoding.code, |         encoding = encoding.code, | ||||||
|         message(encoding, subject, body), |         message = message(encoding, subject, body), | ||||||
|         ackData, |         ackMessage = ackData(type, ackData), | ||||||
|         lazy { Factory.createAck(from, ackData, ttl) }, |         conversationId = conversationId, | ||||||
|         conversationId, |         inventoryVector = null, | ||||||
|         null, |         signature = null, | ||||||
|         null, |         received = null, | ||||||
|         null, |         initialHash = null, | ||||||
|         null, |         ttl = ttl, | ||||||
|         ttl, |         labels = labels, | ||||||
|         labels, |         status = status | ||||||
|         status |  | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     constructor(builder: Builder) : this( |     constructor(builder: Builder) : this( | ||||||
|         builder.type, |         type = builder.type, | ||||||
|         builder.from ?: throw IllegalStateException("sender identity not set"), |         from = builder.from ?: throw IllegalStateException("sender identity not set"), | ||||||
|         builder.to, |         to = builder.to, | ||||||
|         builder.encoding, |         encodingCode = builder.encoding, | ||||||
|         builder.message, |         message = builder.message, | ||||||
|         builder.ackData, |         ackData = builder.ackData, | ||||||
|         lazy { |         ackMessage = lazy { | ||||||
|             val ackMsg = builder.ackMessage |             val ackMsg = builder.ackMessage | ||||||
|             if (ackMsg != null && ackMsg.isNotEmpty()) { |             if (ackMsg != null && ackMsg.isNotEmpty()) { | ||||||
|                 Factory.getObjectMessage( |                 Factory.getObjectMessage( | ||||||
| @@ -236,15 +245,17 @@ class Plaintext private constructor( | |||||||
|                 Factory.createAck(builder.from!!, builder.ackData, builder.ttl) |                 Factory.createAck(builder.from!!, builder.ackData, builder.ttl) | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         builder.conversation ?: UUID.randomUUID(), |         conversationId = builder.conversation ?: UUID.randomUUID(), | ||||||
|         builder.inventoryVector, |         inventoryVector = builder.inventoryVector, | ||||||
|         builder.signature, |         signature = builder.signature, | ||||||
|         builder.received, |         received = builder.received, | ||||||
|         null, |         initialHash = null, | ||||||
|         builder.ttl, |         ttl = builder.ttl, | ||||||
|         builder.labels, |         labels = builder.labels, | ||||||
|         builder.status ?: Status.RECEIVED |         status = builder.status ?: Status.RECEIVED | ||||||
|     ) |     ) { | ||||||
|  |         id = builder.id | ||||||
|  |     } | ||||||
|  |  | ||||||
|     fun write(out: OutputStream, includeSignature: Boolean) { |     fun write(out: OutputStream, includeSignature: Boolean) { | ||||||
|         Encode.varInt(from.version, out) |         Encode.varInt(from.version, out) | ||||||
| @@ -259,7 +270,7 @@ class Plaintext private constructor( | |||||||
|                 Encode.varInt(0, out) |                 Encode.varInt(0, out) | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             Encode.int32(from.pubkey!!.behaviorBitfield.toLong(), out) |             Encode.int32(from.pubkey!!.behaviorBitfield, out) | ||||||
|             out.write(from.pubkey!!.signingKey, 1, 64) |             out.write(from.pubkey!!.signingKey, 1, 64) | ||||||
|             out.write(from.pubkey!!.encryptionKey, 1, 64) |             out.write(from.pubkey!!.encryptionKey, 1, 64) | ||||||
|             if (from.version >= 3) { |             if (from.version >= 3) { | ||||||
| @@ -267,13 +278,13 @@ class Plaintext private constructor( | |||||||
|                 Encode.varInt(from.pubkey!!.extraBytes, out) |                 Encode.varInt(from.pubkey!!.extraBytes, out) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         if (type == Type.MSG) { |         if (type == MSG) { | ||||||
|             out.write(to!!.ripe) |             out.write(to!!.ripe) | ||||||
|         } |         } | ||||||
|         Encode.varInt(encodingCode, out) |         Encode.varInt(encodingCode, out) | ||||||
|         Encode.varInt(message.size.toLong(), out) |         Encode.varInt(message.size, out) | ||||||
|         out.write(message) |         out.write(message) | ||||||
|         if (type == Type.MSG) { |         if (type == MSG) { | ||||||
|             if (to?.has(Feature.DOES_ACK) ?: false) { |             if (to?.has(Feature.DOES_ACK) ?: false) { | ||||||
|                 val ack = ByteArrayOutputStream() |                 val ack = ByteArrayOutputStream() | ||||||
|                 ackMessage?.write(ack) |                 ackMessage?.write(ack) | ||||||
| @@ -286,8 +297,7 @@ class Plaintext private constructor( | |||||||
|             if (signature == null) { |             if (signature == null) { | ||||||
|                 Encode.varInt(0, out) |                 Encode.varInt(0, out) | ||||||
|             } else { |             } else { | ||||||
|                 Encode.varInt(signature!!.size.toLong(), out) |                 Encode.varBytes(signature!!, out) | ||||||
|                 out.write(signature!!) |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -305,7 +315,7 @@ class Plaintext private constructor( | |||||||
|                 Encode.varInt(0, buffer) |                 Encode.varInt(0, buffer) | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             Encode.int32(from.pubkey!!.behaviorBitfield.toLong(), buffer) |             Encode.int32(from.pubkey!!.behaviorBitfield, buffer) | ||||||
|             buffer.put(from.pubkey!!.signingKey, 1, 64) |             buffer.put(from.pubkey!!.signingKey, 1, 64) | ||||||
|             buffer.put(from.pubkey!!.encryptionKey, 1, 64) |             buffer.put(from.pubkey!!.encryptionKey, 1, 64) | ||||||
|             if (from.version >= 3) { |             if (from.version >= 3) { | ||||||
| @@ -313,13 +323,12 @@ class Plaintext private constructor( | |||||||
|                 Encode.varInt(from.pubkey!!.extraBytes, buffer) |                 Encode.varInt(from.pubkey!!.extraBytes, buffer) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         if (type == Type.MSG) { |         if (type == MSG) { | ||||||
|             buffer.put(to!!.ripe) |             buffer.put(to!!.ripe) | ||||||
|         } |         } | ||||||
|         Encode.varInt(encodingCode, buffer) |         Encode.varInt(encodingCode, buffer) | ||||||
|         Encode.varInt(message.size.toLong(), buffer) |         Encode.varBytes(message, buffer) | ||||||
|         buffer.put(message) |         if (type == MSG) { | ||||||
|         if (type == Type.MSG) { |  | ||||||
|             if (to!!.has(Feature.DOES_ACK) && ackMessage != null) { |             if (to!!.has(Feature.DOES_ACK) && ackMessage != null) { | ||||||
|                 Encode.varBytes(Encode.bytes(ackMessage!!), buffer) |                 Encode.varBytes(Encode.bytes(ackMessage!!), buffer) | ||||||
|             } else { |             } else { | ||||||
| @@ -331,8 +340,7 @@ class Plaintext private constructor( | |||||||
|             if (sig == null) { |             if (sig == null) { | ||||||
|                 Encode.varInt(0, buffer) |                 Encode.varInt(0, buffer) | ||||||
|             } else { |             } else { | ||||||
|                 Encode.varInt(sig.size.toLong(), buffer) |                 Encode.varBytes(sig, buffer) | ||||||
|                 buffer.put(sig) |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -365,7 +373,7 @@ class Plaintext private constructor( | |||||||
|             val firstLine = s.nextLine() |             val firstLine = s.nextLine() | ||||||
|             if (encodingCode == EXTENDED.code) { |             if (encodingCode == EXTENDED.code) { | ||||||
|                 if (Message.TYPE == extendedData?.type) { |                 if (Message.TYPE == extendedData?.type) { | ||||||
|                     return (extendedData!!.content as Message?)?.subject |                     return (extendedData!!.content as? Message)?.subject | ||||||
|                 } else { |                 } else { | ||||||
|                     return null |                     return null | ||||||
|                 } |                 } | ||||||
| @@ -551,10 +559,12 @@ class Plaintext private constructor( | |||||||
|             return this |             return this | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         fun to(address: BitmessageAddress): Builder { |         fun to(address: BitmessageAddress?): Builder { | ||||||
|             if (type != Type.MSG && to != null) |             if (address != null) { | ||||||
|  |                 if (type != MSG && to != null) | ||||||
|                     throw IllegalArgumentException("recipient address only allowed for msg") |                     throw IllegalArgumentException("recipient address only allowed for msg") | ||||||
|                 to = address |                 to = address | ||||||
|  |             } | ||||||
|             return this |             return this | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -594,7 +604,7 @@ class Plaintext private constructor( | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         fun destinationRipe(ripe: ByteArray?): Builder { |         fun destinationRipe(ripe: ByteArray?): Builder { | ||||||
|             if (type != Type.MSG && ripe != null) throw IllegalArgumentException("ripe only allowed for msg") |             if (type != MSG && ripe != null) throw IllegalArgumentException("ripe only allowed for msg") | ||||||
|             this.destinationRipe = ripe |             this.destinationRipe = ripe | ||||||
|             return this |             return this | ||||||
|         } |         } | ||||||
| @@ -622,7 +632,6 @@ class Plaintext private constructor( | |||||||
|             } catch (e: UnsupportedEncodingException) { |             } catch (e: UnsupportedEncodingException) { | ||||||
|                 throw ApplicationException(e) |                 throw ApplicationException(e) | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             return this |             return this | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -632,13 +641,13 @@ class Plaintext private constructor( | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         fun ackMessage(ack: ByteArray?): Builder { |         fun ackMessage(ack: ByteArray?): Builder { | ||||||
|             if (type != Type.MSG && ack != null) throw IllegalArgumentException("ackMessage only allowed for msg") |             if (type != MSG && ack != null) throw IllegalArgumentException("ackMessage only allowed for msg") | ||||||
|             this.ackMessage = ack |             this.ackMessage = ack | ||||||
|             return this |             return this | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         fun ackData(ackData: ByteArray?): Builder { |         fun ackData(ackData: ByteArray?): Builder { | ||||||
|             if (type != Type.MSG && ackData != null) |             if (type != MSG && ackData != null) | ||||||
|                 throw IllegalArgumentException("ackMessage only allowed for msg") |                 throw IllegalArgumentException("ackMessage only allowed for msg") | ||||||
|             this.ackData = ackData |             this.ackData = ackData | ||||||
|             return this |             return this | ||||||
| @@ -704,7 +713,7 @@ class Plaintext private constructor( | |||||||
|             if (to == null && type != Type.BROADCAST && destinationRipe != null) { |             if (to == null && type != Type.BROADCAST && destinationRipe != null) { | ||||||
|                 to = BitmessageAddress(0, 0, destinationRipe!!) |                 to = BitmessageAddress(0, 0, destinationRipe!!) | ||||||
|             } |             } | ||||||
|             if (type == Type.MSG && ackMessage == null && ackData == null) { |             if (type == MSG && ackMessage == null && ackData == null) { | ||||||
|                 ackData = cryptography().randomBytes(Msg.ACK_LENGTH) |                 ackData = cryptography().randomBytes(Msg.ACK_LENGTH) | ||||||
|             } |             } | ||||||
|             if (ttl <= 0) { |             if (ttl <= 0) { | ||||||
| @@ -733,10 +742,10 @@ class Plaintext private constructor( | |||||||
|                 .publicEncryptionKey(Decode.bytes(`in`, 64)) |                 .publicEncryptionKey(Decode.bytes(`in`, 64)) | ||||||
|                 .nonceTrialsPerByte(if (version >= 3) Decode.varInt(`in`) else 0) |                 .nonceTrialsPerByte(if (version >= 3) Decode.varInt(`in`) else 0) | ||||||
|                 .extraBytes(if (version >= 3) Decode.varInt(`in`) else 0) |                 .extraBytes(if (version >= 3) Decode.varInt(`in`) else 0) | ||||||
|                 .destinationRipe(if (type == Type.MSG) Decode.bytes(`in`, 20) else null) |                 .destinationRipe(if (type == MSG) Decode.bytes(`in`, 20) else null) | ||||||
|                 .encoding(Decode.varInt(`in`)) |                 .encoding(Decode.varInt(`in`)) | ||||||
|                 .message(Decode.varBytes(`in`)) |                 .message(Decode.varBytes(`in`)) | ||||||
|                 .ackMessage(if (type == Type.MSG) Decode.varBytes(`in`) else null) |                 .ackMessage(if (type == MSG) Decode.varBytes(`in`) else null) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -78,7 +78,7 @@ class Version constructor( | |||||||
|     override val command: MessagePayload.Command = MessagePayload.Command.VERSION |     override val command: MessagePayload.Command = MessagePayload.Command.VERSION | ||||||
|  |  | ||||||
|     override fun write(out: OutputStream) { |     override fun write(out: OutputStream) { | ||||||
|         Encode.int32(version.toLong(), out) |         Encode.int32(version, out) | ||||||
|         Encode.int64(services, out) |         Encode.int64(services, out) | ||||||
|         Encode.int64(timestamp, out) |         Encode.int64(timestamp, out) | ||||||
|         addrRecv.write(out, true) |         addrRecv.write(out, true) | ||||||
| @@ -89,7 +89,7 @@ class Version constructor( | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun write(buffer: ByteBuffer) { |     override fun write(buffer: ByteBuffer) { | ||||||
|         Encode.int32(version.toLong(), buffer) |         Encode.int32(version, buffer) | ||||||
|         Encode.int64(services, buffer) |         Encode.int64(services, buffer) | ||||||
|         Encode.int64(timestamp, buffer) |         Encode.int64(timestamp, buffer) | ||||||
|         addrRecv.write(buffer, true) |         addrRecv.write(buffer, true) | ||||||
|   | |||||||
| @@ -114,7 +114,7 @@ class CryptoBox : Streamable { | |||||||
|  |  | ||||||
|     private fun writeWithoutMAC(out: OutputStream) { |     private fun writeWithoutMAC(out: OutputStream) { | ||||||
|         out.write(initializationVector) |         out.write(initializationVector) | ||||||
|         Encode.int16(curveType.toLong(), out) |         Encode.int16(curveType, out) | ||||||
|         writeCoordinateComponent(out, Points.getX(R)) |         writeCoordinateComponent(out, Points.getX(R)) | ||||||
|         writeCoordinateComponent(out, Points.getY(R)) |         writeCoordinateComponent(out, Points.getY(R)) | ||||||
|         out.write(encrypted) |         out.write(encrypted) | ||||||
| @@ -123,14 +123,14 @@ class CryptoBox : Streamable { | |||||||
|     private fun writeCoordinateComponent(out: OutputStream, x: ByteArray) { |     private fun writeCoordinateComponent(out: OutputStream, x: ByteArray) { | ||||||
|         val offset = Bytes.numberOfLeadingZeros(x) |         val offset = Bytes.numberOfLeadingZeros(x) | ||||||
|         val length = x.size - offset |         val length = x.size - offset | ||||||
|         Encode.int16(length.toLong(), out) |         Encode.int16(length, out) | ||||||
|         out.write(x, offset, length) |         out.write(x, offset, length) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun writeCoordinateComponent(buffer: ByteBuffer, x: ByteArray) { |     private fun writeCoordinateComponent(buffer: ByteBuffer, x: ByteArray) { | ||||||
|         val offset = Bytes.numberOfLeadingZeros(x) |         val offset = Bytes.numberOfLeadingZeros(x) | ||||||
|         val length = x.size - offset |         val length = x.size - offset | ||||||
|         Encode.int16(length.toLong(), buffer) |         Encode.int16(length, buffer) | ||||||
|         buffer.put(x, offset, length) |         buffer.put(x, offset, length) | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -141,7 +141,7 @@ class CryptoBox : Streamable { | |||||||
|  |  | ||||||
|     override fun write(buffer: ByteBuffer) { |     override fun write(buffer: ByteBuffer) { | ||||||
|         buffer.put(initializationVector) |         buffer.put(initializationVector) | ||||||
|         Encode.int16(curveType.toLong(), buffer) |         Encode.int16(curveType, buffer) | ||||||
|         writeCoordinateComponent(buffer, Points.getX(R)) |         writeCoordinateComponent(buffer, Points.getX(R)) | ||||||
|         writeCoordinateComponent(buffer, Points.getY(R)) |         writeCoordinateComponent(buffer, Points.getY(R)) | ||||||
|         buffer.put(encrypted) |         buffer.put(encrypted) | ||||||
|   | |||||||
| @@ -31,13 +31,13 @@ open class V2Pubkey constructor(version: Long, override val stream: Long, overri | |||||||
|     override val encryptionKey: ByteArray = if (encryptionKey.size == 64) add0x04(encryptionKey) else encryptionKey |     override val encryptionKey: ByteArray = if (encryptionKey.size == 64) add0x04(encryptionKey) else encryptionKey | ||||||
|  |  | ||||||
|     override fun write(out: OutputStream) { |     override fun write(out: OutputStream) { | ||||||
|         Encode.int32(behaviorBitfield.toLong(), out) |         Encode.int32(behaviorBitfield, out) | ||||||
|         out.write(signingKey, 1, 64) |         out.write(signingKey, 1, 64) | ||||||
|         out.write(encryptionKey, 1, 64) |         out.write(encryptionKey, 1, 64) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun write(buffer: ByteBuffer) { |     override fun write(buffer: ByteBuffer) { | ||||||
|         Encode.int32(behaviorBitfield.toLong(), buffer) |         Encode.int32(behaviorBitfield, buffer) | ||||||
|         buffer.put(signingKey, 1, 64) |         buffer.put(signingKey, 1, 64) | ||||||
|         buffer.put(encryptionKey, 1, 64) |         buffer.put(encryptionKey, 1, 64) | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -19,11 +19,14 @@ package ch.dissem.bitmessage.entity.valueobject | |||||||
| import java.io.Serializable | import java.io.Serializable | ||||||
| import java.util.* | import java.util.* | ||||||
|  |  | ||||||
| data class Label(private val label: String, val type: Label.Type, | data class Label( | ||||||
|  |     private val label: String, | ||||||
|  |     val type: Label.Type?, | ||||||
|     /** |     /** | ||||||
|      * RGBA representation for the color. |      * RGBA representation for the color. | ||||||
|      */ |      */ | ||||||
|                  var color: Int) : Serializable { |     var color: Int | ||||||
|  | ) : Serializable { | ||||||
|  |  | ||||||
|     var id: Any? = null |     var id: Any? = null | ||||||
|  |  | ||||||
|   | |||||||
| @@ -30,7 +30,7 @@ import java.util.* | |||||||
| /** | /** | ||||||
|  * A node's address. It's written in IPv6 format. |  * A node's address. It's written in IPv6 format. | ||||||
|  */ |  */ | ||||||
| data class NetworkAddress constructor( | data class NetworkAddress( | ||||||
|     var time: Long, |     var time: Long, | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -47,25 +47,25 @@ data class NetworkAddress constructor( | |||||||
|      * IPv6 address. IPv4 addresses are written into the message as a 16 byte IPv4-mapped IPv6 address |      * IPv6 address. IPv4 addresses are written into the message as a 16 byte IPv4-mapped IPv6 address | ||||||
|      * (12 bytes 00 00 00 00 00 00 00 00 00 00 FF FF, followed by the 4 bytes of the IPv4 address). |      * (12 bytes 00 00 00 00 00 00 00 00 00 00 FF FF, followed by the 4 bytes of the IPv4 address). | ||||||
|      */ |      */ | ||||||
|     val iPv6: ByteArray, |     val IPv6: ByteArray, | ||||||
|     val port: Int |     val port: Int | ||||||
| ) : Streamable { | ) : Streamable { | ||||||
|  |  | ||||||
|     fun provides(service: Version.Service?): Boolean = service?.isEnabled(services) ?: false |     fun provides(service: Version.Service?): Boolean = service?.isEnabled(services) ?: false | ||||||
|  |  | ||||||
|     fun toInetAddress(): InetAddress { |     fun toInetAddress(): InetAddress { | ||||||
|         return InetAddress.getByAddress(iPv6) |         return InetAddress.getByAddress(IPv6) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun equals(other: Any?): Boolean { |     override fun equals(other: Any?): Boolean { | ||||||
|         if (this === other) return true |         if (this === other) return true | ||||||
|         if (other !is NetworkAddress) return false |         if (other !is NetworkAddress) return false | ||||||
|  |  | ||||||
|         return port == other.port && Arrays.equals(iPv6, other.iPv6) |         return port == other.port && Arrays.equals(IPv6, other.IPv6) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun hashCode(): Int { |     override fun hashCode(): Int { | ||||||
|         var result = Arrays.hashCode(iPv6) |         var result = Arrays.hashCode(IPv6) | ||||||
|         result = 31 * result + port |         result = 31 * result + port | ||||||
|         return result |         return result | ||||||
|     } |     } | ||||||
| @@ -84,8 +84,8 @@ data class NetworkAddress constructor( | |||||||
|             Encode.int32(stream, out) |             Encode.int32(stream, out) | ||||||
|         } |         } | ||||||
|         Encode.int64(services, out) |         Encode.int64(services, out) | ||||||
|         out.write(iPv6) |         out.write(IPv6) | ||||||
|         Encode.int16(port.toLong(), out) |         Encode.int16(port, out) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun write(buffer: ByteBuffer) { |     override fun write(buffer: ByteBuffer) { | ||||||
| @@ -98,8 +98,8 @@ data class NetworkAddress constructor( | |||||||
|             Encode.int32(stream, buffer) |             Encode.int32(stream, buffer) | ||||||
|         } |         } | ||||||
|         Encode.int64(services, buffer) |         Encode.int64(services, buffer) | ||||||
|         buffer.put(iPv6) |         buffer.put(IPv6) | ||||||
|         Encode.int16(port.toLong(), buffer) |         Encode.int16(port, buffer) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     class Builder { |     class Builder { | ||||||
|   | |||||||
| @@ -121,12 +121,10 @@ class PrivateKey : Streamable { | |||||||
|         Encode.varInt(pubkey.stream, out) |         Encode.varInt(pubkey.stream, out) | ||||||
|         val baos = ByteArrayOutputStream() |         val baos = ByteArrayOutputStream() | ||||||
|         pubkey.writeUnencrypted(baos) |         pubkey.writeUnencrypted(baos) | ||||||
|         Encode.varInt(baos.size().toLong(), out) |         Encode.varInt(baos.size(), out) | ||||||
|         out.write(baos.toByteArray()) |         out.write(baos.toByteArray()) | ||||||
|         Encode.varInt(privateSigningKey.size.toLong(), out) |         Encode.varBytes(privateSigningKey, out) | ||||||
|         out.write(privateSigningKey) |         Encode.varBytes(privateEncryptionKey, out) | ||||||
|         Encode.varInt(privateEncryptionKey.size.toLong(), out) |  | ||||||
|         out.write(privateEncryptionKey) |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -37,7 +37,7 @@ import javax.crypto.spec.SecretKeySpec | |||||||
|  * Implements everything that isn't directly dependent on either Spongy- or Bouncycastle. |  * Implements everything that isn't directly dependent on either Spongy- or Bouncycastle. | ||||||
|  */ |  */ | ||||||
| abstract class AbstractCryptography protected constructor(@JvmField protected val provider: Provider) : Cryptography { | abstract class AbstractCryptography protected constructor(@JvmField protected val provider: Provider) : Cryptography { | ||||||
|     private val context by InternalContext |     private val context by InternalContext.lateinit | ||||||
|  |  | ||||||
|     @JvmField protected val ALGORITHM_ECDSA = "ECDSA" |     @JvmField protected val ALGORITHM_ECDSA = "ECDSA" | ||||||
|     @JvmField protected val ALGORITHM_ECDSA_SHA1 = "SHA1withECDSA" |     @JvmField protected val ALGORITHM_ECDSA_SHA1 = "SHA1withECDSA" | ||||||
| @@ -87,21 +87,21 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va | |||||||
|         return result |         return result | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun doProofOfWork(`object`: ObjectMessage, nonceTrialsPerByte: Long, |     override fun doProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, | ||||||
|                                extraBytes: Long, callback: ProofOfWorkEngine.Callback) { |                                extraBytes: Long, callback: ProofOfWorkEngine.Callback) { | ||||||
|  |  | ||||||
|         val initialHash = getInitialHash(`object`) |         val initialHash = getInitialHash(objectMessage) | ||||||
|  |  | ||||||
|         val target = getProofOfWorkTarget(`object`, |         val target = getProofOfWorkTarget(objectMessage, | ||||||
|             max(nonceTrialsPerByte, NETWORK_NONCE_TRIALS_PER_BYTE), max(extraBytes, NETWORK_EXTRA_BYTES)) |             max(nonceTrialsPerByte, NETWORK_NONCE_TRIALS_PER_BYTE), max(extraBytes, NETWORK_EXTRA_BYTES)) | ||||||
|  |  | ||||||
|         context.proofOfWorkEngine.calculateNonce(initialHash, target, callback) |         context.proofOfWorkEngine.calculateNonce(initialHash, target, callback) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Throws(InsufficientProofOfWorkException::class) |     @Throws(InsufficientProofOfWorkException::class) | ||||||
|     override fun checkProofOfWork(`object`: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) { |     override fun checkProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) { | ||||||
|         val target = getProofOfWorkTarget(`object`, nonceTrialsPerByte, extraBytes) |         val target = getProofOfWorkTarget(objectMessage, nonceTrialsPerByte, extraBytes) | ||||||
|         val value = doubleSha512(`object`.nonce ?: throw ApplicationException("Object without nonce"), getInitialHash(`object`)) |         val value = doubleSha512(objectMessage.nonce ?: throw ApplicationException("Object without nonce"), getInitialHash(objectMessage)) | ||||||
|         if (Bytes.lt(target, value, 8)) { |         if (Bytes.lt(target, value, 8)) { | ||||||
|             throw InsufficientProofOfWorkException(target, value) |             throw InsufficientProofOfWorkException(target, value) | ||||||
|         } |         } | ||||||
| @@ -130,18 +130,18 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va | |||||||
|         return false |         return false | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun getInitialHash(`object`: ObjectMessage): ByteArray { |     override fun getInitialHash(objectMessage: ObjectMessage): ByteArray { | ||||||
|         return sha512(`object`.payloadBytesWithoutNonce) |         return sha512(objectMessage.payloadBytesWithoutNonce) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun getProofOfWorkTarget(`object`: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long): ByteArray { |     override fun getProofOfWorkTarget(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long): ByteArray { | ||||||
|         @Suppress("NAME_SHADOWING") |         @Suppress("NAME_SHADOWING") | ||||||
|         val nonceTrialsPerByte = if (nonceTrialsPerByte == 0L) NETWORK_NONCE_TRIALS_PER_BYTE else nonceTrialsPerByte |         val nonceTrialsPerByte = if (nonceTrialsPerByte == 0L) NETWORK_NONCE_TRIALS_PER_BYTE else nonceTrialsPerByte | ||||||
|         @Suppress("NAME_SHADOWING") |         @Suppress("NAME_SHADOWING") | ||||||
|         val extraBytes = if (extraBytes == 0L) NETWORK_EXTRA_BYTES else extraBytes |         val extraBytes = if (extraBytes == 0L) NETWORK_EXTRA_BYTES else extraBytes | ||||||
|  |  | ||||||
|         val TTL = BigInteger.valueOf(`object`.expiresTime - UnixTime.now) |         val TTL = BigInteger.valueOf(objectMessage.expiresTime - UnixTime.now) | ||||||
|         val powLength = BigInteger.valueOf(`object`.payloadBytesWithoutNonce.size + extraBytes) |         val powLength = BigInteger.valueOf(objectMessage.payloadBytesWithoutNonce.size + extraBytes) | ||||||
|         val denominator = BigInteger.valueOf(nonceTrialsPerByte) |         val denominator = BigInteger.valueOf(nonceTrialsPerByte) | ||||||
|             .multiply( |             .multiply( | ||||||
|                 powLength.add( |                 powLength.add( | ||||||
|   | |||||||
| @@ -28,19 +28,19 @@ import ch.dissem.bitmessage.utils.UnixTime | |||||||
| import java.util.* | import java.util.* | ||||||
|  |  | ||||||
| abstract class AbstractMessageRepository : MessageRepository { | abstract class AbstractMessageRepository : MessageRepository { | ||||||
|     protected var ctx by InternalContext |     protected var ctx by InternalContext.lateinit | ||||||
|  |  | ||||||
|     protected fun saveContactIfNecessary(contact: BitmessageAddress?) { |     protected fun saveContactIfNecessary(contact: BitmessageAddress?) { | ||||||
|         contact?.let { |         contact?.let { | ||||||
|             val savedAddress = ctx.addressRepository.getAddress(contact.address) |             val savedAddress = ctx.addressRepository.getAddress(it.address) | ||||||
|             if (savedAddress == null) { |             if (savedAddress == null) { | ||||||
|                 ctx.addressRepository.save(contact) |                 ctx.addressRepository.save(it) | ||||||
|             } else if (savedAddress.pubkey == null && contact.pubkey != null) { |             } else { | ||||||
|                 savedAddress.pubkey = contact.pubkey |                 if (savedAddress.pubkey == null && it.pubkey != null) { | ||||||
|  |                     savedAddress.pubkey = it.pubkey | ||||||
|                     ctx.addressRepository.save(savedAddress) |                     ctx.addressRepository.save(savedAddress) | ||||||
|                 } |                 } | ||||||
|             if (savedAddress != null) { |                 it.alias = savedAddress.alias | ||||||
|                 contact.alias = savedAddress.alias |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -145,7 +145,7 @@ interface Cryptography { | |||||||
|      * Calculates the proof of work. This might take a long time, depending on the hardware, message size and time to |      * Calculates the proof of work. This might take a long time, depending on the hardware, message size and time to | ||||||
|      * live. |      * live. | ||||||
|  |  | ||||||
|      * @param object             to do the proof of work for |      * @param objectMessage      to do the proof of work for | ||||||
|      * * |      * * | ||||||
|      * @param nonceTrialsPerByte difficulty |      * @param nonceTrialsPerByte difficulty | ||||||
|      * * |      * * | ||||||
| @@ -153,11 +153,11 @@ interface Cryptography { | |||||||
|      * * |      * * | ||||||
|      * @param callback           to handle nonce once it's calculated |      * @param callback           to handle nonce once it's calculated | ||||||
|      */ |      */ | ||||||
|     fun doProofOfWork(`object`: ObjectMessage, nonceTrialsPerByte: Long, |     fun doProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, | ||||||
|                       extraBytes: Long, callback: ProofOfWorkEngine.Callback) |                       extraBytes: Long, callback: ProofOfWorkEngine.Callback) | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * @param object             to be checked |      * @param objectMessage      to be checked | ||||||
|      * * |      * * | ||||||
|      * @param nonceTrialsPerByte difficulty |      * @param nonceTrialsPerByte difficulty | ||||||
|      * * |      * * | ||||||
| @@ -166,11 +166,11 @@ interface Cryptography { | |||||||
|      * @throws InsufficientProofOfWorkException if proof of work doesn't check out (makes it more difficult to send small messages) |      * @throws InsufficientProofOfWorkException if proof of work doesn't check out (makes it more difficult to send small messages) | ||||||
|      */ |      */ | ||||||
|     @Throws(InsufficientProofOfWorkException::class) |     @Throws(InsufficientProofOfWorkException::class) | ||||||
|     fun checkProofOfWork(`object`: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) |     fun checkProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) | ||||||
|  |  | ||||||
|     fun getInitialHash(`object`: ObjectMessage): ByteArray |     fun getInitialHash(objectMessage: ObjectMessage): ByteArray | ||||||
|  |  | ||||||
|     fun getProofOfWorkTarget(`object`: ObjectMessage, nonceTrialsPerByte: Long = NETWORK_NONCE_TRIALS_PER_BYTE, extraBytes: Long = NETWORK_EXTRA_BYTES): ByteArray |     fun getProofOfWorkTarget(objectMessage: ObjectMessage, nonceTrialsPerByte: Long = NETWORK_NONCE_TRIALS_PER_BYTE, extraBytes: Long = NETWORK_EXTRA_BYTES): ByteArray | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Calculates the MAC for a message (data) |      * Calculates the MAC for a message (data) | ||||||
|   | |||||||
| @@ -23,7 +23,7 @@ import ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST | |||||||
| import ch.dissem.bitmessage.entity.valueobject.Label | import ch.dissem.bitmessage.entity.valueobject.Label | ||||||
|  |  | ||||||
| open class DefaultLabeler : Labeler { | open class DefaultLabeler : Labeler { | ||||||
|     private var ctx by InternalContext |     private var ctx by InternalContext.lateinit | ||||||
|  |  | ||||||
|     override fun setLabels(msg: Plaintext) { |     override fun setLabels(msg: Plaintext) { | ||||||
|         msg.status = RECEIVED |         msg.status = RECEIVED | ||||||
|   | |||||||
| @@ -43,9 +43,9 @@ interface Inventory { | |||||||
|      */ |      */ | ||||||
|     fun getObjects(stream: Long, version: Long, vararg types: ObjectType): List<ObjectMessage> |     fun getObjects(stream: Long, version: Long, vararg types: ObjectType): List<ObjectMessage> | ||||||
|  |  | ||||||
|     fun storeObject(`object`: ObjectMessage) |     fun storeObject(objectMessage: ObjectMessage) | ||||||
|  |  | ||||||
|     operator fun contains(`object`: ObjectMessage): Boolean |     operator fun contains(objectMessage: ObjectMessage): Boolean | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Deletes all objects that expired 5 minutes ago or earlier |      * Deletes all objects that expired 5 minutes ago or earlier | ||||||
|   | |||||||
| @@ -28,7 +28,7 @@ interface MessageRepository { | |||||||
|  |  | ||||||
|     fun getLabels(vararg types: Label.Type): List<Label> |     fun getLabels(vararg types: Label.Type): List<Label> | ||||||
|  |  | ||||||
|     fun countUnread(label: Label): Int |     fun countUnread(label: Label?): Int | ||||||
|  |  | ||||||
|     fun getMessage(id: Any): Plaintext |     fun getMessage(id: Any): Plaintext | ||||||
|  |  | ||||||
| @@ -43,7 +43,7 @@ interface MessageRepository { | |||||||
|      * * |      * * | ||||||
|      * @return a distinct list of all conversations that have at least one message with the given label. |      * @return a distinct list of all conversations that have at least one message with the given label. | ||||||
|      */ |      */ | ||||||
|     fun findConversations(label: Label): List<UUID> |     fun findConversations(label: Label?): List<UUID> | ||||||
|  |  | ||||||
|     fun findMessages(label: Label?): List<Plaintext> |     fun findMessages(label: Label?): List<Plaintext> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -81,6 +81,6 @@ interface NetworkHandler { | |||||||
|  |  | ||||||
|     interface MessageListener { |     interface MessageListener { | ||||||
|         @Throws(IOException::class) |         @Throws(IOException::class) | ||||||
|         fun receive(`object`: ObjectMessage) |         fun receive(objectMessage: ObjectMessage) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -29,14 +29,14 @@ interface ProofOfWorkRepository { | |||||||
|  |  | ||||||
|     fun getItems(): List<ByteArray> |     fun getItems(): List<ByteArray> | ||||||
|  |  | ||||||
|     fun putObject(`object`: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) |     fun putObject(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) | ||||||
|  |  | ||||||
|     fun putObject(item: Item) |     fun putObject(item: Item) | ||||||
|  |  | ||||||
|     fun removeObject(initialHash: ByteArray) |     fun removeObject(initialHash: ByteArray) | ||||||
|  |  | ||||||
|     data class Item @JvmOverloads constructor( |     data class Item @JvmOverloads constructor( | ||||||
|         val `object`: ObjectMessage, |         val objectMessage: ObjectMessage, | ||||||
|         val nonceTrialsPerByte: Long, |         val nonceTrialsPerByte: Long, | ||||||
|         val extraBytes: Long, |         val extraBytes: Long, | ||||||
|         // Needed for ACK POW calculation |         // Needed for ACK POW calculation | ||||||
|   | |||||||
| @@ -19,6 +19,7 @@ package ch.dissem.bitmessage.utils | |||||||
| import ch.dissem.bitmessage.entity.Plaintext | import ch.dissem.bitmessage.entity.Plaintext | ||||||
| import ch.dissem.bitmessage.entity.valueobject.InventoryVector | import ch.dissem.bitmessage.entity.valueobject.InventoryVector | ||||||
| import ch.dissem.bitmessage.ports.MessageRepository | import ch.dissem.bitmessage.ports.MessageRepository | ||||||
|  | import org.slf4j.LoggerFactory | ||||||
| import java.util.* | import java.util.* | ||||||
| import java.util.Collections | import java.util.Collections | ||||||
| import java.util.regex.Pattern | import java.util.regex.Pattern | ||||||
|   | |||||||
| @@ -27,19 +27,20 @@ import java.nio.ByteBuffer | |||||||
|  */ |  */ | ||||||
| object Encode { | object Encode { | ||||||
|     @JvmStatic fun varIntList(values: LongArray, stream: OutputStream) { |     @JvmStatic fun varIntList(values: LongArray, stream: OutputStream) { | ||||||
|         varInt(values.size.toLong(), stream) |         varInt(values.size, stream) | ||||||
|         for (value in values) { |         for (value in values) { | ||||||
|             varInt(value, stream) |             varInt(value, stream) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @JvmStatic fun varIntList(values: LongArray, buffer: ByteBuffer) { |     @JvmStatic fun varIntList(values: LongArray, buffer: ByteBuffer) { | ||||||
|         varInt(values.size.toLong(), buffer) |         varInt(values.size, buffer) | ||||||
|         for (value in values) { |         for (value in values) { | ||||||
|             varInt(value, buffer) |             varInt(value, buffer) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @JvmStatic fun varInt(value: Int, buffer: ByteBuffer) = varInt(value.toLong(), buffer) | ||||||
|     @JvmStatic fun varInt(value: Long, buffer: ByteBuffer) { |     @JvmStatic fun varInt(value: Long, buffer: ByteBuffer) { | ||||||
|         if (value < 0) { |         if (value < 0) { | ||||||
|             // This is due to the fact that Java doesn't really support unsigned values. |             // This is due to the fact that Java doesn't really support unsigned values. | ||||||
| @@ -62,6 +63,7 @@ object Encode { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @JvmStatic fun varInt(value: Int) = varInt(value.toLong()) | ||||||
|     @JvmStatic fun varInt(value: Long): ByteArray { |     @JvmStatic fun varInt(value: Long): ByteArray { | ||||||
|         val buffer = ByteBuffer.allocate(9) |         val buffer = ByteBuffer.allocate(9) | ||||||
|         varInt(value, buffer) |         varInt(value, buffer) | ||||||
| @@ -69,6 +71,7 @@ object Encode { | |||||||
|         return Bytes.truncate(buffer.array(), buffer.limit()) |         return Bytes.truncate(buffer.array(), buffer.limit()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @JvmStatic @JvmOverloads fun varInt(value: Int, stream: OutputStream, counter: AccessCounter? = null) = varInt(value.toLong(), stream, counter) | ||||||
|     @JvmStatic @JvmOverloads fun varInt(value: Long, stream: OutputStream, counter: AccessCounter? = null) { |     @JvmStatic @JvmOverloads fun varInt(value: Long, stream: OutputStream, counter: AccessCounter? = null) { | ||||||
|         val buffer = ByteBuffer.allocate(9) |         val buffer = ByteBuffer.allocate(9) | ||||||
|         varInt(value, buffer) |         varInt(value, buffer) | ||||||
| @@ -77,27 +80,34 @@ object Encode { | |||||||
|         AccessCounter.inc(counter, buffer.limit()) |         AccessCounter.inc(counter, buffer.limit()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @JvmStatic @JvmOverloads fun int8(value: Long, stream: OutputStream, counter: AccessCounter? = null) { |     @JvmStatic @JvmOverloads fun int8(value: Long, stream: OutputStream, counter: AccessCounter? = null) = int8(value.toInt(), stream, counter) | ||||||
|         stream.write(value.toInt()) |     @JvmStatic @JvmOverloads fun int8(value: Int, stream: OutputStream, counter: AccessCounter? = null) { | ||||||
|  |         stream.write(value) | ||||||
|         AccessCounter.inc(counter) |         AccessCounter.inc(counter) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @JvmStatic @JvmOverloads fun int16(value: Long, stream: OutputStream, counter: AccessCounter? = null) { |     @JvmStatic @JvmOverloads fun int16(value: Long, stream: OutputStream, counter: AccessCounter? = null) = int16(value.toShort(), stream, counter) | ||||||
|         stream.write(ByteBuffer.allocate(2).putShort(value.toShort()).array()) |     @JvmStatic @JvmOverloads fun int16(value: Int, stream: OutputStream, counter: AccessCounter? = null) = int16(value.toShort(), stream, counter) | ||||||
|  |     @JvmStatic @JvmOverloads fun int16(value: Short, stream: OutputStream, counter: AccessCounter? = null) { | ||||||
|  |         stream.write(ByteBuffer.allocate(2).putShort(value).array()) | ||||||
|         AccessCounter.inc(counter, 2) |         AccessCounter.inc(counter, 2) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @JvmStatic fun int16(value: Long, buffer: ByteBuffer) { |     @JvmStatic fun int16(value: Long, buffer: ByteBuffer) = int16(value.toShort(), buffer) | ||||||
|         buffer.putShort(value.toShort()) |     @JvmStatic fun int16(value: Int, buffer: ByteBuffer) = int16(value.toShort(), buffer) | ||||||
|  |     @JvmStatic fun int16(value: Short, buffer: ByteBuffer) { | ||||||
|  |         buffer.putShort(value) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @JvmStatic @JvmOverloads fun int32(value: Long, stream: OutputStream, counter: AccessCounter? = null) { |     @JvmStatic @JvmOverloads fun int32(value: Long, stream: OutputStream, counter: AccessCounter? = null) = int32(value.toInt(), stream, counter) | ||||||
|         stream.write(ByteBuffer.allocate(4).putInt(value.toInt()).array()) |     @JvmStatic @JvmOverloads fun int32(value: Int, stream: OutputStream, counter: AccessCounter? = null) { | ||||||
|  |         stream.write(ByteBuffer.allocate(4).putInt(value).array()) | ||||||
|         AccessCounter.inc(counter, 4) |         AccessCounter.inc(counter, 4) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @JvmStatic fun int32(value: Long, buffer: ByteBuffer) { |     @JvmStatic fun int32(value: Long, buffer: ByteBuffer) = int32(value.toInt(), buffer) | ||||||
|         buffer.putInt(value.toInt()) |     @JvmStatic fun int32(value: Int, buffer: ByteBuffer) { | ||||||
|  |         buffer.putInt(value) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @JvmStatic @JvmOverloads fun int64(value: Long, stream: OutputStream, counter: AccessCounter? = null) { |     @JvmStatic @JvmOverloads fun int64(value: Long, stream: OutputStream, counter: AccessCounter? = null) { | ||||||
|   | |||||||
| @@ -28,8 +28,8 @@ import ch.dissem.bitmessage.ports.DefaultLabeler | |||||||
| import ch.dissem.bitmessage.ports.ProofOfWorkEngine | import ch.dissem.bitmessage.ports.ProofOfWorkEngine | ||||||
| import ch.dissem.bitmessage.ports.ProofOfWorkRepository | import ch.dissem.bitmessage.ports.ProofOfWorkRepository | ||||||
| import ch.dissem.bitmessage.testutils.TestInventory | import ch.dissem.bitmessage.testutils.TestInventory | ||||||
| import ch.dissem.bitmessage.utils.Singleton |  | ||||||
| import ch.dissem.bitmessage.utils.Singleton.cryptography | import ch.dissem.bitmessage.utils.Singleton.cryptography | ||||||
|  | import ch.dissem.bitmessage.utils.Strings.hex | ||||||
| import ch.dissem.bitmessage.utils.TTL | import ch.dissem.bitmessage.utils.TTL | ||||||
| import ch.dissem.bitmessage.utils.TestUtils | import ch.dissem.bitmessage.utils.TestUtils | ||||||
| import ch.dissem.bitmessage.utils.UnixTime.MINUTE | import ch.dissem.bitmessage.utils.UnixTime.MINUTE | ||||||
| @@ -40,12 +40,12 @@ import org.junit.Assert.* | |||||||
| import org.junit.Before | import org.junit.Before | ||||||
| import org.junit.Test | import org.junit.Test | ||||||
| import java.util.* | import java.util.* | ||||||
|  | import kotlin.concurrent.thread | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @author Christian Basler |  * @author Christian Basler | ||||||
|  */ |  */ | ||||||
| class BitmessageContextTest { | class BitmessageContextTest { | ||||||
|     private lateinit var ctx: BitmessageContext |  | ||||||
|     private var listener: BitmessageContext.Listener = mock() |     private var listener: BitmessageContext.Listener = mock() | ||||||
|     private val inventory = spy(TestInventory()) |     private val inventory = spy(TestInventory()) | ||||||
|     private val testPowRepo = spy(object : ProofOfWorkRepository { |     private val testPowRepo = spy(object : ProofOfWorkRepository { | ||||||
| @@ -54,7 +54,7 @@ class BitmessageContextTest { | |||||||
|         internal var removed = 0 |         internal var removed = 0 | ||||||
|  |  | ||||||
|         override fun getItem(initialHash: ByteArray): ProofOfWorkRepository.Item { |         override fun getItem(initialHash: ByteArray): ProofOfWorkRepository.Item { | ||||||
|             return items[InventoryVector(initialHash)]!! |             return items[InventoryVector(initialHash)] ?: throw IllegalArgumentException("${hex(initialHash)} not found in $items") | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         override fun getItems(): List<ByteArray> { |         override fun getItems(): List<ByteArray> { | ||||||
| @@ -66,12 +66,12 @@ class BitmessageContextTest { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         override fun putObject(item: ProofOfWorkRepository.Item) { |         override fun putObject(item: ProofOfWorkRepository.Item) { | ||||||
|             items.put(InventoryVector(cryptography().getInitialHash(item.`object`)), item) |             items.put(InventoryVector(cryptography().getInitialHash(item.objectMessage)), item) | ||||||
|             added++ |             added++ | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         override fun putObject(`object`: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) { |         override fun putObject(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) { | ||||||
|             items.put(InventoryVector(cryptography().getInitialHash(`object`)), ProofOfWorkRepository.Item(`object`, nonceTrialsPerByte, extraBytes)) |             items.put(InventoryVector(cryptography().getInitialHash(objectMessage)), ProofOfWorkRepository.Item(objectMessage, nonceTrialsPerByte, extraBytes)) | ||||||
|             added++ |             added++ | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -87,13 +87,14 @@ class BitmessageContextTest { | |||||||
|             removed = 0 |             removed = 0 | ||||||
|         } |         } | ||||||
|     }) |     }) | ||||||
|  |     private val testPowEngine = spy(object : ProofOfWorkEngine { | ||||||
|     @Before |         override fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: ProofOfWorkEngine.Callback) { | ||||||
|     fun setUp() { |             thread { callback.onNonceCalculated(initialHash, ByteArray(8)) } | ||||||
|         Singleton.initialize(BouncyCryptography()) |         } | ||||||
|         ctx = BitmessageContext.Builder() |     }) | ||||||
|  |     private var ctx = BitmessageContext.Builder() | ||||||
|         .addressRepo(mock()) |         .addressRepo(mock()) | ||||||
|             .cryptography(cryptography()) |         .cryptography(BouncyCryptography()) | ||||||
|         .inventory(inventory) |         .inventory(inventory) | ||||||
|         .listener(listener) |         .listener(listener) | ||||||
|         .messageRepo(mock()) |         .messageRepo(mock()) | ||||||
| @@ -101,25 +102,27 @@ class BitmessageContextTest { | |||||||
|         .nodeRegistry(mock()) |         .nodeRegistry(mock()) | ||||||
|         .labeler(spy(DefaultLabeler())) |         .labeler(spy(DefaultLabeler())) | ||||||
|         .powRepo(testPowRepo) |         .powRepo(testPowRepo) | ||||||
|             .proofOfWorkEngine(spy(object : ProofOfWorkEngine { |         .proofOfWorkEngine(testPowEngine) | ||||||
|                 override fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: ProofOfWorkEngine.Callback) { |  | ||||||
|                     callback.onNonceCalculated(initialHash, ByteArray(8)) |  | ||||||
|                 } |  | ||||||
|             })) |  | ||||||
|         .build() |         .build() | ||||||
|  |  | ||||||
|  |     init { | ||||||
|         TTL.msg = 2 * MINUTE |         TTL.msg = 2 * MINUTE | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Before | ||||||
|  |     fun setUp() { | ||||||
|         testPowRepo.reset() |         testPowRepo.reset() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Test |     @Test | ||||||
|     fun `ensure contact is saved and pubkey requested`() { |     fun `ensure contact is saved and pubkey requested`() { | ||||||
|         val contact = BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT") |         val contact = BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT") | ||||||
|         whenever(ctx.addresses.getAddress(contact.address)).thenReturn(contact) |         doReturn(contact).whenever(ctx.addresses).getAddress(eq(contact.address)) | ||||||
|  |  | ||||||
|         ctx.addContact(contact) |         ctx.addContact(contact) | ||||||
|  |  | ||||||
|         verify(ctx.addresses, timeout(1000).atLeastOnce()).save(contact) |         verify(ctx.addresses, timeout(1000).atLeastOnce()).save(eq(contact)) | ||||||
|         verify(ctx.internals.proofOfWorkEngine, timeout(1000)).calculateNonce(any(), any(), any()) |         verify(testPowEngine, timeout(1000)).calculateNonce(any(), any(), any()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Test |     @Test | ||||||
| @@ -132,7 +135,7 @@ class BitmessageContextTest { | |||||||
|         ctx.addContact(contact) |         ctx.addContact(contact) | ||||||
|  |  | ||||||
|         verify(ctx.addresses, times(1)).save(contact) |         verify(ctx.addresses, times(1)).save(contact) | ||||||
|         verify(ctx.internals.proofOfWorkEngine, never()).calculateNonce(any(), any(), any()) |         verify(testPowEngine, never()).calculateNonce(any(), any(), any()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Test |     @Test | ||||||
| @@ -155,7 +158,7 @@ class BitmessageContextTest { | |||||||
|         ctx.addContact(contact) |         ctx.addContact(contact) | ||||||
|  |  | ||||||
|         verify(ctx.addresses, atLeastOnce()).save(contact) |         verify(ctx.addresses, atLeastOnce()).save(contact) | ||||||
|         verify(ctx.internals.proofOfWorkEngine, never()).calculateNonce(any(), any(), any()) |         verify(testPowEngine, never()).calculateNonce(any(), any(), any()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Test |     @Test | ||||||
| @@ -179,7 +182,7 @@ class BitmessageContextTest { | |||||||
|         ctx.addContact(contact) |         ctx.addContact(contact) | ||||||
|  |  | ||||||
|         verify(ctx.addresses, atLeastOnce()).save(any()) |         verify(ctx.addresses, atLeastOnce()).save(any()) | ||||||
|         verify(ctx.internals.proofOfWorkEngine, never()).calculateNonce(any(), any(), any()) |         verify(testPowEngine, never()).calculateNonce(any(), any(), any()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Test |     @Test | ||||||
| @@ -209,9 +212,9 @@ class BitmessageContextTest { | |||||||
|     fun `ensure message is sent`() { |     fun `ensure message is sent`() { | ||||||
|         ctx.send(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), TestUtils.loadContact(), |         ctx.send(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), TestUtils.loadContact(), | ||||||
|             "Subject", "Message") |             "Subject", "Message") | ||||||
|  |         verify(ctx.internals.proofOfWorkRepository, timeout(10000)).putObject( | ||||||
|  |             argThat { payload.type == ObjectType.MSG }, eq(1000L), eq(1000L)) | ||||||
|         assertEquals(2, testPowRepo.added) |         assertEquals(2, testPowRepo.added) | ||||||
|         verify(ctx.internals.proofOfWorkRepository, timeout(10000).atLeastOnce()) |  | ||||||
|             .putObject(argThat { payload.type == ObjectType.MSG }, eq(1000L), eq(1000L)) |  | ||||||
|         verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat { type == Type.MSG }) |         verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat { type == Type.MSG }) | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -236,10 +239,9 @@ class BitmessageContextTest { | |||||||
|     fun `ensure broadcast is sent`() { |     fun `ensure broadcast is sent`() { | ||||||
|         ctx.broadcast(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), |         ctx.broadcast(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), | ||||||
|             "Subject", "Message") |             "Subject", "Message") | ||||||
|         verify(ctx.internals.proofOfWorkRepository, timeout(10000).atLeastOnce()) |         verify(ctx.internals.proofOfWorkRepository, timeout(1000).atLeastOnce()) | ||||||
|             .putObject(argThat { payload.type == ObjectType.BROADCAST }, eq(1000L), eq(1000L)) |             .putObject(argThat { payload.type == ObjectType.BROADCAST }, eq(1000L), eq(1000L)) | ||||||
|         verify(ctx.internals.proofOfWorkEngine) |         verify(testPowEngine).calculateNonce(any(), any(), any()) | ||||||
|             .calculateNonce(any(), any(), any()) |  | ||||||
|         verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat { type == Type.BROADCAST }) |         verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat { type == Type.BROADCAST }) | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -265,7 +267,7 @@ class BitmessageContextTest { | |||||||
|     fun `ensure deterministic addresses are created`() { |     fun `ensure deterministic addresses are created`() { | ||||||
|         val expected_size = 8 |         val expected_size = 8 | ||||||
|         val addresses = ctx.createDeterministicAddresses("test", expected_size, 4, 1, false) |         val addresses = ctx.createDeterministicAddresses("test", expected_size, 4, 1, false) | ||||||
|         assertEquals(expected_size.toLong(), addresses.size.toLong()) |         assertEquals(expected_size, addresses.size) | ||||||
|         val expected = HashSet<String>(expected_size) |         val expected = HashSet<String>(expected_size) | ||||||
|         expected.add("BM-2cWFkyuXXFw6d393RGnin2RpSXj8wxtt6F") |         expected.add("BM-2cWFkyuXXFw6d393RGnin2RpSXj8wxtt6F") | ||||||
|         expected.add("BM-2cX8TF9vuQZEWvT7UrEeq1HN9dgiSUPLEN") |         expected.add("BM-2cX8TF9vuQZEWvT7UrEeq1HN9dgiSUPLEN") | ||||||
| @@ -285,7 +287,7 @@ class BitmessageContextTest { | |||||||
|     fun `ensure short deterministic addresses are created`() { |     fun `ensure short deterministic addresses are created`() { | ||||||
|         val expected_size = 1 |         val expected_size = 1 | ||||||
|         val addresses = ctx.createDeterministicAddresses("test", expected_size, 4, 1, true) |         val addresses = ctx.createDeterministicAddresses("test", expected_size, 4, 1, true) | ||||||
|         assertEquals(expected_size.toLong(), addresses.size.toLong()) |         assertEquals(expected_size, addresses.size) | ||||||
|         val expected = HashSet<String>(expected_size) |         val expected = HashSet<String>(expected_size) | ||||||
|         expected.add("BM-NBGyBAEp6VnBkFWKpzUSgxuTqVdWPi78") |         expected.add("BM-NBGyBAEp6VnBkFWKpzUSgxuTqVdWPi78") | ||||||
|         for (a in addresses) { |         for (a in addresses) { | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ | |||||||
|  |  | ||||||
| package ch.dissem.bitmessage | package ch.dissem.bitmessage | ||||||
|  |  | ||||||
|  | import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography | ||||||
| import ch.dissem.bitmessage.entity.BitmessageAddress | import ch.dissem.bitmessage.entity.BitmessageAddress | ||||||
| import ch.dissem.bitmessage.entity.ObjectMessage | import ch.dissem.bitmessage.entity.ObjectMessage | ||||||
| import ch.dissem.bitmessage.entity.Plaintext | import ch.dissem.bitmessage.entity.Plaintext | ||||||
| @@ -26,9 +27,12 @@ import ch.dissem.bitmessage.entity.payload.GetPubkey | |||||||
| import ch.dissem.bitmessage.entity.payload.Msg | import ch.dissem.bitmessage.entity.payload.Msg | ||||||
| import ch.dissem.bitmessage.entity.payload.ObjectType | import ch.dissem.bitmessage.entity.payload.ObjectType | ||||||
| import ch.dissem.bitmessage.factory.Factory | import ch.dissem.bitmessage.factory.Factory | ||||||
|  | import ch.dissem.bitmessage.ports.ProofOfWorkRepository | ||||||
| import ch.dissem.bitmessage.utils.Singleton | import ch.dissem.bitmessage.utils.Singleton | ||||||
| import ch.dissem.bitmessage.utils.TestBase | import ch.dissem.bitmessage.utils.TestBase | ||||||
| import ch.dissem.bitmessage.utils.TestUtils | import ch.dissem.bitmessage.utils.TestUtils | ||||||
|  | import ch.dissem.bitmessage.utils.UnixTime.MINUTE | ||||||
|  | import ch.dissem.bitmessage.utils.UnixTime.now | ||||||
| import com.nhaarman.mockito_kotlin.* | import com.nhaarman.mockito_kotlin.* | ||||||
| import org.junit.Before | import org.junit.Before | ||||||
| import org.junit.Test | import org.junit.Test | ||||||
| @@ -40,7 +44,7 @@ class DefaultMessageListenerTest : TestBase() { | |||||||
|     private lateinit var listener: DefaultMessageListener |     private lateinit var listener: DefaultMessageListener | ||||||
|  |  | ||||||
|     private val ctx = TestUtils.mockedInternalContext( |     private val ctx = TestUtils.mockedInternalContext( | ||||||
|         cryptography = Singleton.cryptography() |         cryptography = BouncyCryptography() | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     @Before |     @Before | ||||||
| @@ -52,10 +56,13 @@ class DefaultMessageListenerTest : TestBase() { | |||||||
|     fun `ensure pubkey is sent on request`() { |     fun `ensure pubkey is sent on request`() { | ||||||
|         val identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8") |         val identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8") | ||||||
|         whenever(ctx.addressRepository.findIdentity(any())).thenReturn(identity) |         whenever(ctx.addressRepository.findIdentity(any())).thenReturn(identity) | ||||||
|         listener.receive(ObjectMessage.Builder() |         val objectMessage = ObjectMessage( | ||||||
|             .stream(2) |             stream = 2, | ||||||
|             .payload(GetPubkey(BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))) |             payload = GetPubkey(BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")), | ||||||
|             .build()) |             expiresTime = now + MINUTE | ||||||
|  |         ) | ||||||
|  |         whenever(ctx.proofOfWorkRepository.getItem(any())).thenReturn(ProofOfWorkRepository.Item(objectMessage, 1000L, 1000L)) | ||||||
|  |         listener.receive(objectMessage) | ||||||
|         verify(ctx.proofOfWorkRepository).putObject(argThat { type == ObjectType.PUBKEY.number }, any(), any()) |         verify(ctx.proofOfWorkRepository).putObject(argThat { type == ObjectType.PUBKEY.number }, any(), any()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -73,6 +80,7 @@ class DefaultMessageListenerTest : TestBase() { | |||||||
|             .build() |             .build() | ||||||
|         objectMessage.sign(identity.privateKey!!) |         objectMessage.sign(identity.privateKey!!) | ||||||
|         objectMessage.encrypt(Singleton.cryptography().createPublicKey(identity.publicDecryptionKey)) |         objectMessage.encrypt(Singleton.cryptography().createPublicKey(identity.publicDecryptionKey)) | ||||||
|  |         whenever(ctx.proofOfWorkRepository.getItem(any())).thenReturn(ProofOfWorkRepository.Item(objectMessage, 1000L, 1000L)) | ||||||
|         listener.receive(objectMessage) |         listener.receive(objectMessage) | ||||||
|  |  | ||||||
|         verify(ctx.addressRepository).save(eq(contact)) |         verify(ctx.addressRepository).save(eq(contact)) | ||||||
|   | |||||||
| @@ -77,24 +77,24 @@ class ProofOfWorkServiceTest { | |||||||
|         val identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8") |         val identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8") | ||||||
|         val address = TestUtils.loadContact() |         val address = TestUtils.loadContact() | ||||||
|         val plaintext = Plaintext.Builder(MSG).from(identity).to(address).message("", "").build() |         val plaintext = Plaintext.Builder(MSG).from(identity).to(address).message("", "").build() | ||||||
|         val `object` = ObjectMessage( |         val objectMessage = ObjectMessage( | ||||||
|             expiresTime = 0, |             expiresTime = 0, | ||||||
|             stream = 1, |             stream = 1, | ||||||
|             payload = Msg(plaintext) |             payload = Msg(plaintext) | ||||||
|         ) |         ) | ||||||
|         `object`.sign(identity.privateKey!!) |         objectMessage.sign(identity.privateKey!!) | ||||||
|         `object`.encrypt(address.pubkey!!) |         objectMessage.encrypt(address.pubkey!!) | ||||||
|         val initialHash = ByteArray(64) |         val initialHash = ByteArray(64) | ||||||
|         val nonce = byteArrayOf(1, 2, 3, 4, 5, 6, 7, 8) |         val nonce = byteArrayOf(1, 2, 3, 4, 5, 6, 7, 8) | ||||||
|  |  | ||||||
|         whenever(ctx.proofOfWorkRepository.getItem(initialHash)).thenReturn(ProofOfWorkRepository.Item(`object`, 1001, 1002)) |         whenever(ctx.proofOfWorkRepository.getItem(initialHash)).thenReturn(ProofOfWorkRepository.Item(objectMessage, 1001, 1002)) | ||||||
|         whenever(ctx.messageRepository.getMessage(initialHash)).thenReturn(plaintext) |         whenever(ctx.messageRepository.getMessage(initialHash)).thenReturn(plaintext) | ||||||
|  |  | ||||||
|         ctx.proofOfWorkService.onNonceCalculated(initialHash, nonce) |         ctx.proofOfWorkService.onNonceCalculated(initialHash, nonce) | ||||||
|  |  | ||||||
|         verify(ctx.proofOfWorkRepository).removeObject(eq(initialHash)) |         verify(ctx.proofOfWorkRepository).removeObject(eq(initialHash)) | ||||||
|         verify(ctx.inventory).storeObject(eq(`object`)) |         verify(ctx.inventory).storeObject(eq(objectMessage)) | ||||||
|         verify(ctx.networkHandler).offer(eq(`object`.inventoryVector)) |         verify(ctx.networkHandler).offer(eq(objectMessage.inventoryVector)) | ||||||
|         assertThat(plaintext.inventoryVector, equalTo(`object`.inventoryVector)) |         assertThat(plaintext.inventoryVector, equalTo(objectMessage.inventoryVector)) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -29,9 +29,9 @@ import org.junit.Test | |||||||
| class SignatureTest : TestBase() { | class SignatureTest : TestBase() { | ||||||
|     @Test |     @Test | ||||||
|     fun `ensure validation works`() { |     fun `ensure validation works`() { | ||||||
|         val `object` = TestUtils.loadObjectMessage(3, "V3Pubkey.payload") |         val objectMessage = TestUtils.loadObjectMessage(3, "V3Pubkey.payload") | ||||||
|         val pubkey = `object`.payload as Pubkey |         val pubkey = objectMessage.payload as Pubkey | ||||||
|         assertTrue(`object`.isSignatureValid(pubkey)) |         assertTrue(objectMessage.isSignatureValid(pubkey)) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Test |     @Test | ||||||
| @@ -52,11 +52,11 @@ class SignatureTest : TestBase() { | |||||||
|     fun `ensure message is properly signed`() { |     fun `ensure message is properly signed`() { | ||||||
|         val identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8") |         val identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8") | ||||||
|  |  | ||||||
|         val `object` = TestUtils.loadObjectMessage(3, "V1Msg.payload") |         val objectMessage = TestUtils.loadObjectMessage(3, "V1Msg.payload") | ||||||
|         val msg = `object`.payload as Msg |         val msg = objectMessage.payload as Msg | ||||||
|         msg.decrypt(identity.privateKey!!.privateEncryptionKey) |         msg.decrypt(identity.privateKey!!.privateEncryptionKey) | ||||||
|         assertNotNull(msg.plaintext) |         assertNotNull(msg.plaintext) | ||||||
|         assertEquals(TestUtils.loadContact().pubkey, msg.plaintext!!.from.pubkey) |         assertEquals(TestUtils.loadContact().pubkey, msg.plaintext!!.from.pubkey) | ||||||
|         assertTrue(`object`.isSignatureValid(msg.plaintext!!.from.pubkey!!)) |         assertTrue(objectMessage.isSignatureValid(msg.plaintext!!.from.pubkey!!)) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -31,9 +31,9 @@ import java.util.* | |||||||
| class BitmessageAddressTest : TestBase() { | class BitmessageAddressTest : TestBase() { | ||||||
|     @Test |     @Test | ||||||
|     fun `ensure feature flag is calculated correctly`() { |     fun `ensure feature flag is calculated correctly`() { | ||||||
|         Assert.assertEquals(1, Pubkey.Feature.bitfield(DOES_ACK).toLong()) |         Assert.assertEquals(1, Pubkey.Feature.bitfield(DOES_ACK)) | ||||||
|         assertEquals(2, Pubkey.Feature.bitfield(INCLUDE_DESTINATION).toLong()) |         assertEquals(2, Pubkey.Feature.bitfield(INCLUDE_DESTINATION)) | ||||||
|         assertEquals(3, Pubkey.Feature.bitfield(DOES_ACK, INCLUDE_DESTINATION).toLong()) |         assertEquals(3, Pubkey.Feature.bitfield(DOES_ACK, INCLUDE_DESTINATION)) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Test |     @Test | ||||||
| @@ -84,9 +84,9 @@ class BitmessageAddressTest : TestBase() { | |||||||
|         val address = BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ") |         val address = BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ") | ||||||
|         Assert.assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), address.ripe) |         Assert.assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), address.ripe) | ||||||
|  |  | ||||||
|         val `object` = TestUtils.loadObjectMessage(3, "V3Pubkey.payload") |         val objectMessage = TestUtils.loadObjectMessage(3, "V3Pubkey.payload") | ||||||
|         val pubkey = `object`.payload as Pubkey |         val pubkey = objectMessage.payload as Pubkey | ||||||
|         assertTrue(`object`.isSignatureValid(pubkey)) |         assertTrue(objectMessage.isSignatureValid(pubkey)) | ||||||
|         try { |         try { | ||||||
|             address.pubkey = pubkey |             address.pubkey = pubkey | ||||||
|         } catch (e: Exception) { |         } catch (e: Exception) { | ||||||
| @@ -100,10 +100,10 @@ class BitmessageAddressTest : TestBase() { | |||||||
|     @Test |     @Test | ||||||
|     fun `ensure V4Pubkey can be imported`() { |     fun `ensure V4Pubkey can be imported`() { | ||||||
|         val address = BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h") |         val address = BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h") | ||||||
|         val `object` = TestUtils.loadObjectMessage(4, "V4Pubkey.payload") |         val objectMessage = TestUtils.loadObjectMessage(4, "V4Pubkey.payload") | ||||||
|         `object`.decrypt(address.publicDecryptionKey) |         objectMessage.decrypt(address.publicDecryptionKey) | ||||||
|         val pubkey = `object`.payload as V4Pubkey |         val pubkey = objectMessage.payload as V4Pubkey | ||||||
|         assertTrue(`object`.isSignatureValid(pubkey)) |         assertTrue(objectMessage.isSignatureValid(pubkey)) | ||||||
|         try { |         try { | ||||||
|             address.pubkey = pubkey |             address.pubkey = pubkey | ||||||
|         } catch (e: Exception) { |         } catch (e: Exception) { | ||||||
|   | |||||||
| @@ -170,12 +170,12 @@ class SerializationTest : TestBase() { | |||||||
|     private fun doTest(resourceName: String, version: Int, expectedPayloadType: Class<*>) { |     private fun doTest(resourceName: String, version: Int, expectedPayloadType: Class<*>) { | ||||||
|         val data = TestUtils.getBytes(resourceName) |         val data = TestUtils.getBytes(resourceName) | ||||||
|         val `in` = ByteArrayInputStream(data) |         val `in` = ByteArrayInputStream(data) | ||||||
|         val `object` = Factory.getObjectMessage(version, `in`, data.size) |         val objectMessage = Factory.getObjectMessage(version, `in`, data.size) | ||||||
|         val out = ByteArrayOutputStream() |         val out = ByteArrayOutputStream() | ||||||
|         assertNotNull(`object`) |         assertNotNull(objectMessage) | ||||||
|         `object`!!.write(out) |         objectMessage!!.write(out) | ||||||
|         assertArrayEquals(data, out.toByteArray()) |         assertArrayEquals(data, out.toByteArray()) | ||||||
|         assertEquals(expectedPayloadType.canonicalName, `object`.payload.javaClass.canonicalName) |         assertEquals(expectedPayloadType.canonicalName, objectMessage.payload.javaClass.canonicalName) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Test |     @Test | ||||||
|   | |||||||
| @@ -42,12 +42,12 @@ class TestInventory : Inventory { | |||||||
|         return ArrayList(inventory.values) |         return ArrayList(inventory.values) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun storeObject(`object`: ObjectMessage) { |     override fun storeObject(objectMessage: ObjectMessage) { | ||||||
|         inventory.put(`object`.inventoryVector, `object`) |         inventory.put(objectMessage.inventoryVector, objectMessage) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun contains(`object`: ObjectMessage): Boolean { |     override fun contains(objectMessage: ObjectMessage): Boolean { | ||||||
|         return inventory.containsKey(`object`.inventoryVector) |         return inventory.containsKey(objectMessage.inventoryVector) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun cleanup() { |     override fun cleanup() { | ||||||
|   | |||||||
| @@ -27,6 +27,6 @@ class CollectionsTest { | |||||||
|     fun `ensure select random returns maximum possible items`() { |     fun `ensure select random returns maximum possible items`() { | ||||||
|         val list = LinkedList<Int>() |         val list = LinkedList<Int>() | ||||||
|         list += 0..9 |         list += 0..9 | ||||||
|         assertEquals(9, Collections.selectRandom(9, list).size.toLong()) |         assertEquals(9, Collections.selectRandom(9, list).size) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -16,7 +16,6 @@ | |||||||
|  |  | ||||||
| package ch.dissem.bitmessage.utils | package ch.dissem.bitmessage.utils | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography |  | ||||||
| import ch.dissem.bitmessage.entity.BitmessageAddress | import ch.dissem.bitmessage.entity.BitmessageAddress | ||||||
| import ch.dissem.bitmessage.entity.Plaintext | import ch.dissem.bitmessage.entity.Plaintext | ||||||
| import ch.dissem.bitmessage.entity.Plaintext.Type.MSG | import ch.dissem.bitmessage.entity.Plaintext.Type.MSG | ||||||
| @@ -30,19 +29,13 @@ import org.junit.Assert.assertThat | |||||||
| import org.junit.Test | import org.junit.Test | ||||||
| import java.util.* | import java.util.* | ||||||
|  |  | ||||||
| class ConversationServiceTest { | class ConversationServiceTest : TestBase() { | ||||||
|     private val alice = BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8") |     private val alice = BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8") | ||||||
|     private val bob = BitmessageAddress("BM-2cTtkBnb4BUYDndTKun6D9PjtueP2h1bQj") |     private val bob = BitmessageAddress("BM-2cTtkBnb4BUYDndTKun6D9PjtueP2h1bQj") | ||||||
|  |  | ||||||
|     private val messageRepository = mock<MessageRepository>() |     private val messageRepository = mock<MessageRepository>() | ||||||
|     private val conversationService = spy(ConversationService(messageRepository)) |     private val conversationService = spy(ConversationService(messageRepository)) | ||||||
|  |  | ||||||
|     companion object { |  | ||||||
|         init { |  | ||||||
|             Singleton.initialize(BouncyCryptography()) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Test |     @Test | ||||||
|     fun `ensure conversation is sorted properly`() { |     fun `ensure conversation is sorted properly`() { | ||||||
|         MockitoKotlin.registerInstanceCreator { UUID.randomUUID() } |         MockitoKotlin.registerInstanceCreator { UUID.randomUUID() } | ||||||
|   | |||||||
| @@ -111,11 +111,11 @@ class EncodeTest { | |||||||
|  |  | ||||||
|  |  | ||||||
|     fun checkBytes(stream: ByteArrayOutputStream, vararg bytes: Int) { |     fun checkBytes(stream: ByteArrayOutputStream, vararg bytes: Int) { | ||||||
|         assertEquals(bytes.size.toLong(), stream.size().toLong()) |         assertEquals(bytes.size, stream.size()) | ||||||
|         val streamBytes = stream.toByteArray() |         val streamBytes = stream.toByteArray() | ||||||
|  |  | ||||||
|         for (i in bytes.indices) { |         for (i in bytes.indices) { | ||||||
|             assertEquals(bytes[i].toByte().toLong(), streamBytes[i].toLong()) |             assertEquals(bytes[i].toByte(), streamBytes[i]) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -43,7 +43,7 @@ object TestUtils { | |||||||
|  |  | ||||||
|     @JvmStatic fun int16(number: Int): ByteArray { |     @JvmStatic fun int16(number: Int): ByteArray { | ||||||
|         val out = ByteArrayOutputStream() |         val out = ByteArrayOutputStream() | ||||||
|         Encode.int16(number.toLong(), out) |         Encode.int16(number, out) | ||||||
|         return out.toByteArray() |         return out.toByteArray() | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -85,9 +85,9 @@ object TestUtils { | |||||||
|     @Throws(DecryptionFailedException::class) |     @Throws(DecryptionFailedException::class) | ||||||
|     @JvmStatic fun loadContact(): BitmessageAddress { |     @JvmStatic fun loadContact(): BitmessageAddress { | ||||||
|         val address = BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h") |         val address = BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h") | ||||||
|         val `object` = TestUtils.loadObjectMessage(3, "V4Pubkey.payload") |         val objectMessage = TestUtils.loadObjectMessage(3, "V4Pubkey.payload") | ||||||
|         `object`.decrypt(address.publicDecryptionKey) |         objectMessage.decrypt(address.publicDecryptionKey) | ||||||
|         address.pubkey = `object`.payload as V4Pubkey |         address.pubkey = objectMessage.payload as V4Pubkey | ||||||
|         return address |         return address | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -40,8 +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), |  * As Spongycastle can't be used on the Oracle JVM, and Bouncycastle doesn't work properly on Android (thanks, Google), | ||||||
|  * this is the Bouncycastle implementation. |  * this is the Bouncycastle implementation. | ||||||
|  */ |  */ | ||||||
| object BouncyCryptography : AbstractCryptography(BouncyCastleProvider()) { | class BouncyCryptography : AbstractCryptography(BouncyCastleProvider()) { | ||||||
|     private val EC_CURVE_PARAMETERS = CustomNamedCurves.getByName("secp256k1") |  | ||||||
|  |  | ||||||
|     override fun crypt(encrypt: Boolean, data: ByteArray, key_e: ByteArray, initializationVector: ByteArray): ByteArray { |     override fun crypt(encrypt: Boolean, data: ByteArray, key_e: ByteArray, initializationVector: ByteArray): ByteArray { | ||||||
|         val cipher = PaddedBufferedBlockCipher( |         val cipher = PaddedBufferedBlockCipher( | ||||||
| @@ -119,4 +118,8 @@ object BouncyCryptography : AbstractCryptography(BouncyCastleProvider()) { | |||||||
|             BigInteger(1, y) |             BigInteger(1, y) | ||||||
|         ).getEncoded(false) |         ).getEncoded(false) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         private val EC_CURVE_PARAMETERS = CustomNamedCurves.getByName("secp256k1") | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -163,7 +163,7 @@ class CryptographyTest { | |||||||
|         val TEST_RIPEMD160 = DatatypeConverter.parseHexBinary("" |         val TEST_RIPEMD160 = DatatypeConverter.parseHexBinary("" | ||||||
|             + "cd566972b5e50104011a92b59fa8e0b1234851ae") |             + "cd566972b5e50104011a92b59fa8e0b1234851ae") | ||||||
|  |  | ||||||
|         private val crypto = BouncyCryptography |         private val crypto = BouncyCryptography() | ||||||
|  |  | ||||||
|         init { |         init { | ||||||
|             Singleton.initialize(crypto) |             Singleton.initialize(crypto) | ||||||
|   | |||||||
| @@ -40,8 +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), |  * 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. |  * this is the Spongycastle implementation. | ||||||
|  */ |  */ | ||||||
| object SpongyCryptography : AbstractCryptography(BouncyCastleProvider()) { | class SpongyCryptography : AbstractCryptography(BouncyCastleProvider()) { | ||||||
|     private val EC_CURVE_PARAMETERS = CustomNamedCurves.getByName("secp256k1") |  | ||||||
|  |  | ||||||
|     override fun crypt(encrypt: Boolean, data: ByteArray, key_e: ByteArray, initializationVector: ByteArray): ByteArray { |     override fun crypt(encrypt: Boolean, data: ByteArray, key_e: ByteArray, initializationVector: ByteArray): ByteArray { | ||||||
|         val cipher = PaddedBufferedBlockCipher( |         val cipher = PaddedBufferedBlockCipher( | ||||||
| @@ -119,4 +118,8 @@ object SpongyCryptography : AbstractCryptography(BouncyCastleProvider()) { | |||||||
|             BigInteger(1, y) |             BigInteger(1, y) | ||||||
|         ).getEncoded(false) |         ).getEncoded(false) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         private val EC_CURVE_PARAMETERS = CustomNamedCurves.getByName("secp256k1") | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -162,7 +162,7 @@ class CryptographyTest { | |||||||
|         val TEST_RIPEMD160 = DatatypeConverter.parseHexBinary("" |         val TEST_RIPEMD160 = DatatypeConverter.parseHexBinary("" | ||||||
|             + "cd566972b5e50104011a92b59fa8e0b1234851ae") |             + "cd566972b5e50104011a92b59fa8e0b1234851ae") | ||||||
|  |  | ||||||
|         private val crypto = SpongyCryptography |         private val crypto = SpongyCryptography() | ||||||
|  |  | ||||||
|         init { |         init { | ||||||
|             Singleton.initialize(crypto) |             Singleton.initialize(crypto) | ||||||
|   | |||||||
| @@ -61,7 +61,7 @@ public class Main { | |||||||
|             .messageRepo(new JdbcMessageRepository(jdbcConfig)) |             .messageRepo(new JdbcMessageRepository(jdbcConfig)) | ||||||
|             .powRepo(new JdbcProofOfWorkRepository(jdbcConfig)) |             .powRepo(new JdbcProofOfWorkRepository(jdbcConfig)) | ||||||
|             .networkHandler(new NioNetworkHandler()) |             .networkHandler(new NioNetworkHandler()) | ||||||
|             .cryptography(BouncyCryptography.INSTANCE) |             .cryptography(new BouncyCryptography()) | ||||||
|             .port(48444); |             .port(48444); | ||||||
|         if (options.localPort != null) { |         if (options.localPort != null) { | ||||||
|             ctxBuilder.nodeRegistry(new NodeRegistry() { |             ctxBuilder.nodeRegistry(new NodeRegistry() { | ||||||
|   | |||||||
| @@ -19,7 +19,6 @@ package ch.dissem.bitmessage; | |||||||
| import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography; | import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography; | ||||||
| import ch.dissem.bitmessage.entity.BitmessageAddress; | import ch.dissem.bitmessage.entity.BitmessageAddress; | ||||||
| import ch.dissem.bitmessage.entity.Plaintext; | import ch.dissem.bitmessage.entity.Plaintext; | ||||||
| import ch.dissem.bitmessage.networking.DefaultNetworkHandler; |  | ||||||
| import ch.dissem.bitmessage.networking.nio.NioNetworkHandler; | import ch.dissem.bitmessage.networking.nio.NioNetworkHandler; | ||||||
| import ch.dissem.bitmessage.ports.DefaultLabeler; | import ch.dissem.bitmessage.ports.DefaultLabeler; | ||||||
| import ch.dissem.bitmessage.ports.Labeler; | import ch.dissem.bitmessage.ports.Labeler; | ||||||
| @@ -29,14 +28,10 @@ import ch.dissem.bitmessage.utils.TTL; | |||||||
| import org.junit.After; | import org.junit.After; | ||||||
| import org.junit.Before; | import org.junit.Before; | ||||||
| import org.junit.Test; | import org.junit.Test; | ||||||
| import org.junit.runner.RunWith; |  | ||||||
| import org.junit.runners.Parameterized; |  | ||||||
| import org.mockito.Mockito; | import org.mockito.Mockito; | ||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
| import java.util.Arrays; |  | ||||||
| import java.util.List; |  | ||||||
| import java.util.UUID; | import java.util.UUID; | ||||||
| import java.util.concurrent.TimeUnit; | import java.util.concurrent.TimeUnit; | ||||||
|  |  | ||||||
| @@ -49,43 +44,29 @@ import static org.mockito.ArgumentMatchers.any; | |||||||
| /** | /** | ||||||
|  * @author Christian Basler |  * @author Christian Basler | ||||||
|  */ |  */ | ||||||
| @RunWith(Parameterized.class) |  | ||||||
| public class SystemTest { | public class SystemTest { | ||||||
|     private static int port = 6000; |     private static int port = 6000; | ||||||
|     private final NetworkHandler aliceNetworkHandler; |  | ||||||
|     private final NetworkHandler bobNetworkHandler; |  | ||||||
|  |  | ||||||
|     private BitmessageContext alice; |     private BitmessageContext alice; | ||||||
|     private TestListener aliceListener = new TestListener(); |  | ||||||
|     private Labeler aliceLabeler = Mockito.spy(new DebugLabeler("Alice")); |  | ||||||
|     private BitmessageAddress aliceIdentity; |     private BitmessageAddress aliceIdentity; | ||||||
|  |     private Labeler aliceLabeler; | ||||||
|  |  | ||||||
|     private BitmessageContext bob; |     private BitmessageContext bob; | ||||||
|     private TestListener bobListener = new TestListener(); |     private TestListener bobListener; | ||||||
|     private BitmessageAddress bobIdentity; |     private BitmessageAddress bobIdentity; | ||||||
|  |  | ||||||
|     public SystemTest(NetworkHandler peer, NetworkHandler node) { |  | ||||||
|         this.aliceNetworkHandler = peer; |  | ||||||
|         this.bobNetworkHandler = node; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Parameterized.Parameters |  | ||||||
|     @SuppressWarnings("deprecation") |  | ||||||
|     public static List<Object[]> parameters() { |  | ||||||
|         return Arrays.asList(new Object[][]{ |  | ||||||
|             {new NioNetworkHandler(), new DefaultNetworkHandler()}, |  | ||||||
|             {new NioNetworkHandler(), new NioNetworkHandler()} |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Before |     @Before | ||||||
|     public void setUp() { |     public void setUp() { | ||||||
|         int alicePort = port++; |  | ||||||
|         int bobPort = port++; |  | ||||||
|         TTL.msg(5 * MINUTE); |         TTL.msg(5 * MINUTE); | ||||||
|         TTL.getpubkey(5 * MINUTE); |         TTL.getpubkey(5 * MINUTE); | ||||||
|         TTL.pubkey(5 * MINUTE); |         TTL.pubkey(5 * MINUTE); | ||||||
|  |  | ||||||
|  |         int alicePort = port++; | ||||||
|  |         int bobPort = port++; | ||||||
|  |         { | ||||||
|             JdbcConfig aliceDB = new JdbcConfig("jdbc:h2:mem:alice;DB_CLOSE_DELAY=-1", "sa", ""); |             JdbcConfig aliceDB = new JdbcConfig("jdbc:h2:mem:alice;DB_CLOSE_DELAY=-1", "sa", ""); | ||||||
|  |             aliceLabeler = Mockito.spy(new DebugLabeler("Alice")); | ||||||
|  |             TestListener aliceListener = new TestListener(); | ||||||
|             alice = new BitmessageContext.Builder() |             alice = new BitmessageContext.Builder() | ||||||
|                 .addressRepo(new JdbcAddressRepository(aliceDB)) |                 .addressRepo(new JdbcAddressRepository(aliceDB)) | ||||||
|                 .inventory(new JdbcInventory(aliceDB)) |                 .inventory(new JdbcInventory(aliceDB)) | ||||||
| @@ -93,15 +74,17 @@ public class SystemTest { | |||||||
|                 .powRepo(new JdbcProofOfWorkRepository(aliceDB)) |                 .powRepo(new JdbcProofOfWorkRepository(aliceDB)) | ||||||
|                 .port(alicePort) |                 .port(alicePort) | ||||||
|                 .nodeRegistry(new TestNodeRegistry(bobPort)) |                 .nodeRegistry(new TestNodeRegistry(bobPort)) | ||||||
|             .networkHandler(aliceNetworkHandler) |                 .networkHandler(new NioNetworkHandler()) | ||||||
|             .cryptography(BouncyCryptography.INSTANCE) |                 .cryptography(new BouncyCryptography()) | ||||||
|                 .listener(aliceListener) |                 .listener(aliceListener) | ||||||
|                 .labeler(aliceLabeler) |                 .labeler(aliceLabeler) | ||||||
|                 .build(); |                 .build(); | ||||||
|             alice.startup(); |             alice.startup(); | ||||||
|             aliceIdentity = alice.createIdentity(false, DOES_ACK); |             aliceIdentity = alice.createIdentity(false, DOES_ACK); | ||||||
|  |         } | ||||||
|  |         { | ||||||
|             JdbcConfig bobDB = new JdbcConfig("jdbc:h2:mem:bob;DB_CLOSE_DELAY=-1", "sa", ""); |             JdbcConfig bobDB = new JdbcConfig("jdbc:h2:mem:bob;DB_CLOSE_DELAY=-1", "sa", ""); | ||||||
|  |             bobListener = new TestListener(); | ||||||
|             bob = new BitmessageContext.Builder() |             bob = new BitmessageContext.Builder() | ||||||
|                 .addressRepo(new JdbcAddressRepository(bobDB)) |                 .addressRepo(new JdbcAddressRepository(bobDB)) | ||||||
|                 .inventory(new JdbcInventory(bobDB)) |                 .inventory(new JdbcInventory(bobDB)) | ||||||
| @@ -109,14 +92,14 @@ public class SystemTest { | |||||||
|                 .powRepo(new JdbcProofOfWorkRepository(bobDB)) |                 .powRepo(new JdbcProofOfWorkRepository(bobDB)) | ||||||
|                 .port(bobPort) |                 .port(bobPort) | ||||||
|                 .nodeRegistry(new TestNodeRegistry(alicePort)) |                 .nodeRegistry(new TestNodeRegistry(alicePort)) | ||||||
|             .networkHandler(bobNetworkHandler) |                 .networkHandler(new NioNetworkHandler()) | ||||||
|             .cryptography(BouncyCryptography.INSTANCE) |                 .cryptography(new BouncyCryptography()) | ||||||
|                 .listener(bobListener) |                 .listener(bobListener) | ||||||
|                 .labeler(new DebugLabeler("Bob")) |                 .labeler(new DebugLabeler("Bob")) | ||||||
|                 .build(); |                 .build(); | ||||||
|             bob.startup(); |             bob.startup(); | ||||||
|             bobIdentity = bob.createIdentity(false, DOES_ACK); |             bobIdentity = bob.createIdentity(false, DOES_ACK); | ||||||
|  |         } | ||||||
|         ((DebugLabeler) alice.labeler()).init(aliceIdentity, bobIdentity); |         ((DebugLabeler) alice.labeler()).init(aliceIdentity, bobIdentity); | ||||||
|         ((DebugLabeler) bob.labeler()).init(aliceIdentity, bobIdentity); |         ((DebugLabeler) bob.labeler()).init(aliceIdentity, bobIdentity); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -65,7 +65,7 @@ class CryptoCustomMessage<T : Streamable> : CustomMessage { | |||||||
|  |  | ||||||
|         Encode.varInt(identity.version, out) |         Encode.varInt(identity.version, out) | ||||||
|         Encode.varInt(identity.stream, out) |         Encode.varInt(identity.stream, out) | ||||||
|         Encode.int32(privateKey.pubkey.behaviorBitfield.toLong(), out) |         Encode.int32(privateKey.pubkey.behaviorBitfield, out) | ||||||
|         out.write(privateKey.pubkey.signingKey, 1, 64) |         out.write(privateKey.pubkey.signingKey, 1, 64) | ||||||
|         out.write(privateKey.pubkey.encryptionKey, 1, 64) |         out.write(privateKey.pubkey.encryptionKey, 1, 64) | ||||||
|         if (identity.version >= 3) { |         if (identity.version >= 3) { | ||||||
| @@ -114,7 +114,7 @@ class CryptoCustomMessage<T : Streamable> : CustomMessage { | |||||||
|         container?.write(out) ?: throw IllegalStateException("not encrypted yet") |         container?.write(out) ?: throw IllegalStateException("not encrypted yet") | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     interface Reader<T> { |     interface Reader<out T> { | ||||||
|         fun read(sender: BitmessageAddress, `in`: InputStream): T |         fun read(sender: BitmessageAddress, `in`: InputStream): T | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -25,10 +25,10 @@ import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; | |||||||
| import ch.dissem.bitmessage.exception.ApplicationException; | import ch.dissem.bitmessage.exception.ApplicationException; | ||||||
| import ch.dissem.bitmessage.exception.NodeException; | import ch.dissem.bitmessage.exception.NodeException; | ||||||
| import ch.dissem.bitmessage.factory.V3MessageReader; | import ch.dissem.bitmessage.factory.V3MessageReader; | ||||||
| import ch.dissem.bitmessage.networking.AbstractConnection; |  | ||||||
| import ch.dissem.bitmessage.ports.NetworkHandler; | import ch.dissem.bitmessage.ports.NetworkHandler; | ||||||
| import ch.dissem.bitmessage.utils.DebugUtils; | import ch.dissem.bitmessage.utils.DebugUtils; | ||||||
| import ch.dissem.bitmessage.utils.Property; | import ch.dissem.bitmessage.utils.Property; | ||||||
|  | import org.jetbrains.annotations.NotNull; | ||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
| @@ -75,8 +75,9 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex | |||||||
|  |  | ||||||
|     private Thread starter; |     private Thread starter; | ||||||
|  |  | ||||||
|  |     @NotNull | ||||||
|     @Override |     @Override | ||||||
|     public Future<Void> synchronize(final InetAddress server, final int port, final long timeoutInSeconds) { |     public Future<Void> synchronize(@NotNull final InetAddress server, final int port, final long timeoutInSeconds) { | ||||||
|         return threadPool.submit(new Callable<Void>() { |         return threadPool.submit(new Callable<Void>() { | ||||||
|             @Override |             @Override | ||||||
|             public Void call() throws Exception { |             public Void call() throws Exception { | ||||||
| @@ -97,8 +98,9 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @NotNull | ||||||
|     @Override |     @Override | ||||||
|     public CustomMessage send(InetAddress server, int port, CustomMessage request) { |     public CustomMessage send(@NotNull InetAddress server, int port, @NotNull CustomMessage request) { | ||||||
|         try (SocketChannel channel = SocketChannel.open(new InetSocketAddress(server, port))) { |         try (SocketChannel channel = SocketChannel.open(new InetSocketAddress(server, port))) { | ||||||
|             channel.configureBlocking(true); |             channel.configureBlocking(true); | ||||||
|             ByteBuffer headerBuffer = ByteBuffer.allocate(HEADER_SIZE); |             ByteBuffer headerBuffer = ByteBuffer.allocate(HEADER_SIZE); | ||||||
| @@ -129,7 +131,7 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex | |||||||
|             if (networkMessage != null && networkMessage.getPayload() instanceof CustomMessage) { |             if (networkMessage != null && networkMessage.getPayload() instanceof CustomMessage) { | ||||||
|                 return (CustomMessage) networkMessage.getPayload(); |                 return (CustomMessage) networkMessage.getPayload(); | ||||||
|             } else { |             } else { | ||||||
|                 if (networkMessage == null || networkMessage.getPayload() == null) { |                 if (networkMessage == null) { | ||||||
|                     throw new NodeException("Empty response from node " + server); |                     throw new NodeException("Empty response from node " + server); | ||||||
|                 } else { |                 } else { | ||||||
|                     throw new NodeException("Unexpected response from node " + server + ": " |                     throw new NodeException("Unexpected response from node " + server + ": " | ||||||
| @@ -391,7 +393,7 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void offer(InventoryVector iv) { |     public void offer(@NotNull InventoryVector iv) { | ||||||
|         List<ConnectionInfo> target = new LinkedList<>(); |         List<ConnectionInfo> target = new LinkedList<>(); | ||||||
|         for (ConnectionInfo connection : connections.keySet()) { |         for (ConnectionInfo connection : connections.keySet()) { | ||||||
|             if (connection.getState() == ACTIVE && !connection.knowsOf(iv)) { |             if (connection.getState() == ACTIVE && !connection.knowsOf(iv)) { | ||||||
| @@ -405,7 +407,7 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void request(Collection<InventoryVector> inventoryVectors) { |     public void request(@NotNull Collection<InventoryVector> inventoryVectors) { | ||||||
|         if (!isRunning()) { |         if (!isRunning()) { | ||||||
|             requestedObjects.clear(); |             requestedObjects.clear(); | ||||||
|             return; |             return; | ||||||
| @@ -468,6 +470,7 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @NotNull | ||||||
|     @Override |     @Override | ||||||
|     public Property getNetworkStatus() { |     public Property getNetworkStatus() { | ||||||
|         TreeSet<Long> streams = new TreeSet<>(); |         TreeSet<Long> streams = new TreeSet<>(); | ||||||
| @@ -520,7 +523,7 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void setContext(InternalContext context) { |     public void setContext(@NotNull InternalContext context) { | ||||||
|         this.ctx = context; |         this.ctx = context; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -98,7 +98,7 @@ public class NetworkHandlerTest { | |||||||
|             .port(peerAddress.getPort()) |             .port(peerAddress.getPort()) | ||||||
|             .nodeRegistry(new TestNodeRegistry()) |             .nodeRegistry(new TestNodeRegistry()) | ||||||
|             .networkHandler(peerNetworkHandler) |             .networkHandler(peerNetworkHandler) | ||||||
|             .cryptography(BouncyCryptography.INSTANCE) |             .cryptography(new BouncyCryptography()) | ||||||
|             .listener(mock(BitmessageContext.Listener.class)) |             .listener(mock(BitmessageContext.Listener.class)) | ||||||
|             .customCommandHandler(new CustomCommandHandler() { |             .customCommandHandler(new CustomCommandHandler() { | ||||||
|                 @Override |                 @Override | ||||||
| @@ -133,7 +133,7 @@ public class NetworkHandlerTest { | |||||||
|             .port(6002) |             .port(6002) | ||||||
|             .nodeRegistry(new TestNodeRegistry(peerAddress)) |             .nodeRegistry(new TestNodeRegistry(peerAddress)) | ||||||
|             .networkHandler(nodeNetworkHandler) |             .networkHandler(nodeNetworkHandler) | ||||||
|             .cryptography(BouncyCryptography.INSTANCE) |             .cryptography(new BouncyCryptography()) | ||||||
|             .listener(mock(BitmessageContext.Listener.class)) |             .listener(mock(BitmessageContext.Listener.class)) | ||||||
|             .build(); |             .build(); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -21,6 +21,7 @@ import ch.dissem.bitmessage.entity.payload.Pubkey; | |||||||
| import ch.dissem.bitmessage.entity.payload.V3Pubkey; | import ch.dissem.bitmessage.entity.payload.V3Pubkey; | ||||||
| import ch.dissem.bitmessage.entity.payload.V4Pubkey; | import ch.dissem.bitmessage.entity.payload.V4Pubkey; | ||||||
| import ch.dissem.bitmessage.entity.valueobject.PrivateKey; | import ch.dissem.bitmessage.entity.valueobject.PrivateKey; | ||||||
|  | import ch.dissem.bitmessage.exception.ApplicationException; | ||||||
| import ch.dissem.bitmessage.factory.Factory; | import ch.dissem.bitmessage.factory.Factory; | ||||||
| import ch.dissem.bitmessage.ports.AddressRepository; | import ch.dissem.bitmessage.ports.AddressRepository; | ||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| @@ -137,16 +138,14 @@ public class JdbcAddressRepository extends JdbcHelper implements AddressReposito | |||||||
|         try ( |         try ( | ||||||
|             Connection connection = config.getConnection(); |             Connection connection = config.getConnection(); | ||||||
|             Statement stmt = connection.createStatement(); |             Statement stmt = connection.createStatement(); | ||||||
|             ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM Address " + |             ResultSet rs = stmt.executeQuery("SELECT '1' FROM Address " + | ||||||
|                 "WHERE address='" + address.getAddress() + "'") |                 "WHERE address='" + address.getAddress() + "'") | ||||||
|         ) { |         ) { | ||||||
|             if (rs.next()) { |             return rs.next(); | ||||||
|                 return rs.getInt(1) > 0; |  | ||||||
|             } |  | ||||||
|         } catch (SQLException e) { |         } catch (SQLException e) { | ||||||
|             LOG.error(e.getMessage(), e); |             LOG.error(e.getMessage(), e); | ||||||
|  |             throw new ApplicationException(e); | ||||||
|         } |         } | ||||||
|         return false; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|   | |||||||
| @@ -1,344 +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.repository; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.entity.Plaintext; |  | ||||||
| import ch.dissem.bitmessage.entity.valueobject.InventoryVector; |  | ||||||
| import ch.dissem.bitmessage.entity.valueobject.Label; |  | ||||||
| import ch.dissem.bitmessage.exception.ApplicationException; |  | ||||||
| import ch.dissem.bitmessage.ports.AbstractMessageRepository; |  | ||||||
| import ch.dissem.bitmessage.ports.MessageRepository; |  | ||||||
| import org.slf4j.Logger; |  | ||||||
| import org.slf4j.LoggerFactory; |  | ||||||
|  |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.io.InputStream; |  | ||||||
| import java.sql.*; |  | ||||||
| import java.util.ArrayList; |  | ||||||
| import java.util.LinkedList; |  | ||||||
| import java.util.List; |  | ||||||
| import java.util.UUID; |  | ||||||
|  |  | ||||||
| import static ch.dissem.bitmessage.repository.JdbcHelper.writeBlob; |  | ||||||
|  |  | ||||||
| public class JdbcMessageRepository extends AbstractMessageRepository implements MessageRepository { |  | ||||||
|     private static final Logger LOG = LoggerFactory.getLogger(JdbcMessageRepository.class); |  | ||||||
|  |  | ||||||
|     private final JdbcConfig config; |  | ||||||
|  |  | ||||||
|     public JdbcMessageRepository(JdbcConfig config) { |  | ||||||
|         this.config = config; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     protected List<Label> findLabels(String where) { |  | ||||||
|         try ( |  | ||||||
|             Connection connection = config.getConnection() |  | ||||||
|         ) { |  | ||||||
|             return findLabels(connection, where); |  | ||||||
|         } catch (SQLException e) { |  | ||||||
|             LOG.error(e.getMessage(), e); |  | ||||||
|         } |  | ||||||
|         return new ArrayList<>(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private Label getLabel(ResultSet rs) throws SQLException { |  | ||||||
|         String typeName = rs.getString("type"); |  | ||||||
|         Label.Type type = null; |  | ||||||
|         if (typeName != null) { |  | ||||||
|             type = Label.Type.valueOf(typeName); |  | ||||||
|         } |  | ||||||
|         Label label = new Label(rs.getString("label"), type, rs.getInt("color")); |  | ||||||
|         label.setId(rs.getLong("id")); |  | ||||||
|  |  | ||||||
|         return label; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public int countUnread(Label label) { |  | ||||||
|         String where; |  | ||||||
|         if (label == null) { |  | ||||||
|             where = ""; |  | ||||||
|         } else { |  | ||||||
|             where = "id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId() + ") AND "; |  | ||||||
|         } |  | ||||||
|         where += "id IN (SELECT message_id FROM Message_Label WHERE label_id IN (" + |  | ||||||
|             "SELECT id FROM Label WHERE type = '" + Label.Type.UNREAD.name() + "'))"; |  | ||||||
|  |  | ||||||
|         try ( |  | ||||||
|             Connection connection = config.getConnection(); |  | ||||||
|             Statement stmt = connection.createStatement(); |  | ||||||
|             ResultSet rs = stmt.executeQuery("SELECT count(*) FROM Message WHERE " + where) |  | ||||||
|         ) { |  | ||||||
|             if (rs.next()) { |  | ||||||
|                 return rs.getInt(1); |  | ||||||
|             } |  | ||||||
|         } catch (SQLException e) { |  | ||||||
|             LOG.error(e.getMessage(), e); |  | ||||||
|         } |  | ||||||
|         return 0; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     protected List<Plaintext> find(String where) { |  | ||||||
|         List<Plaintext> result = new LinkedList<>(); |  | ||||||
|         try ( |  | ||||||
|             Connection connection = config.getConnection(); |  | ||||||
|             Statement stmt = connection.createStatement(); |  | ||||||
|             ResultSet rs = stmt.executeQuery( |  | ||||||
|                 "SELECT id, iv, type, sender, recipient, data, ack_data, sent, received, initial_hash, status, ttl, retries, next_try, conversation " + |  | ||||||
|                     "FROM Message WHERE " + where) |  | ||||||
|         ) { |  | ||||||
|             while (rs.next()) { |  | ||||||
|                 byte[] iv = rs.getBytes("iv"); |  | ||||||
|                 InputStream data = rs.getBinaryStream("data"); |  | ||||||
|                 Plaintext.Type type = Plaintext.Type.valueOf(rs.getString("type")); |  | ||||||
|                 Plaintext.Builder builder = Plaintext.readWithoutSignature(type, data); |  | ||||||
|                 long id = rs.getLong("id"); |  | ||||||
|                 builder.id(id); |  | ||||||
|                 builder.IV(InventoryVector.fromHash(iv)); |  | ||||||
|                 builder.from(getCtx().getAddressRepository().getAddress(rs.getString("sender"))); |  | ||||||
|                 builder.to(getCtx().getAddressRepository().getAddress(rs.getString("recipient"))); |  | ||||||
|                 builder.ackData(rs.getBytes("ack_data")); |  | ||||||
|                 builder.sent(rs.getObject("sent", Long.class)); |  | ||||||
|                 builder.received(rs.getObject("received", Long.class)); |  | ||||||
|                 builder.status(Plaintext.Status.valueOf(rs.getString("status"))); |  | ||||||
|                 builder.ttl(rs.getLong("ttl")); |  | ||||||
|                 builder.retries(rs.getInt("retries")); |  | ||||||
|                 builder.nextTry(rs.getObject("next_try", Long.class)); |  | ||||||
|                 builder.conversation(rs.getObject("conversation", UUID.class)); |  | ||||||
|                 builder.labels(findLabels(connection, |  | ||||||
|                     "id IN (SELECT label_id FROM Message_Label WHERE message_id=" + id + ") ORDER BY ord")); |  | ||||||
|                 Plaintext message = builder.build(); |  | ||||||
|                 message.setInitialHash(rs.getBytes("initial_hash")); |  | ||||||
|                 result.add(message); |  | ||||||
|             } |  | ||||||
|         } catch (SQLException e) { |  | ||||||
|             LOG.error(e.getMessage(), e); |  | ||||||
|         } |  | ||||||
|         return result; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private List<Label> findLabels(Connection connection, String where) { |  | ||||||
|         List<Label> result = new ArrayList<>(); |  | ||||||
|         try ( |  | ||||||
|             Statement stmt = connection.createStatement(); |  | ||||||
|             ResultSet rs = stmt.executeQuery("SELECT id, label, type, color FROM Label WHERE " + where) |  | ||||||
|         ) { |  | ||||||
|             while (rs.next()) { |  | ||||||
|                 result.add(getLabel(rs)); |  | ||||||
|             } |  | ||||||
|         } catch (SQLException e) { |  | ||||||
|             LOG.error(e.getMessage(), e); |  | ||||||
|         } |  | ||||||
|         return result; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void save(Plaintext message) { |  | ||||||
|         saveContactIfNecessary(message.getFrom()); |  | ||||||
|         saveContactIfNecessary(message.getTo()); |  | ||||||
|  |  | ||||||
|         try (Connection connection = config.getConnection()) { |  | ||||||
|             try { |  | ||||||
|                 connection.setAutoCommit(false); |  | ||||||
|                 save(connection, message); |  | ||||||
|                 updateParents(connection, message); |  | ||||||
|                 updateLabels(connection, message); |  | ||||||
|                 connection.commit(); |  | ||||||
|             } catch (IOException | SQLException e) { |  | ||||||
|                 connection.rollback(); |  | ||||||
|                 throw e; |  | ||||||
|             } |  | ||||||
|         } catch (IOException | SQLException e) { |  | ||||||
|             throw new ApplicationException(e); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private void save(Connection connection, Plaintext message) throws IOException, SQLException { |  | ||||||
|         if (message.getId() == null) { |  | ||||||
|             insert(connection, message); |  | ||||||
|         } else { |  | ||||||
|             update(connection, message); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private void updateLabels(Connection connection, Plaintext message) throws SQLException { |  | ||||||
|         // remove existing labels |  | ||||||
|         try (Statement stmt = connection.createStatement()) { |  | ||||||
|             stmt.executeUpdate("DELETE FROM Message_Label WHERE message_id=" + message.getId()); |  | ||||||
|         } |  | ||||||
|         // save new labels |  | ||||||
|         try (PreparedStatement ps = connection.prepareStatement("INSERT INTO Message_Label VALUES (" + |  | ||||||
|             message.getId() + ", ?)")) { |  | ||||||
|             for (Label label : message.getLabels()) { |  | ||||||
|                 ps.setLong(1, (Long) label.getId()); |  | ||||||
|                 ps.executeUpdate(); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private void updateParents(Connection connection, Plaintext message) throws SQLException { |  | ||||||
|         if (message.getInventoryVector() == null || message.getParents().isEmpty()) { |  | ||||||
|             // There are no parents to save yet (they are saved in the extended data, that's enough for now) |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|         // remove existing parents |  | ||||||
|         try (PreparedStatement ps = connection.prepareStatement("DELETE FROM Message_Parent WHERE child=?")) { |  | ||||||
|             ps.setBytes(1, message.getInitialHash()); |  | ||||||
|             ps.executeUpdate(); |  | ||||||
|         } |  | ||||||
|         byte[] childIV = message.getInventoryVector().getHash(); |  | ||||||
|         // save new parents |  | ||||||
|         int order = 0; |  | ||||||
|         try (PreparedStatement ps = connection.prepareStatement("INSERT INTO Message_Parent VALUES (?, ?, ?, ?)")) { |  | ||||||
|             for (InventoryVector parentIV : message.getParents()) { |  | ||||||
|                 Plaintext parent = getMessage(parentIV); |  | ||||||
|                 mergeConversations(connection, parent.getConversationId(), message.getConversationId()); |  | ||||||
|                 order++; |  | ||||||
|                 ps.setBytes(1, parentIV.getHash()); |  | ||||||
|                 ps.setBytes(2, childIV); |  | ||||||
|                 ps.setInt(3, order); // FIXME: this might not be necessary |  | ||||||
|                 ps.setObject(4, message.getConversationId()); |  | ||||||
|                 ps.executeUpdate(); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private void insert(Connection connection, Plaintext message) throws SQLException, IOException { |  | ||||||
|         try (PreparedStatement ps = connection.prepareStatement( |  | ||||||
|             "INSERT INTO Message (iv, type, sender, recipient, data, ack_data, sent, received, " + |  | ||||||
|                 "status, initial_hash, ttl, retries, next_try, conversation) " + |  | ||||||
|                 "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", |  | ||||||
|             Statement.RETURN_GENERATED_KEYS) |  | ||||||
|         ) { |  | ||||||
|             ps.setBytes(1, message.getInventoryVector() == null ? null : message.getInventoryVector().getHash()); |  | ||||||
|             ps.setString(2, message.getType().name()); |  | ||||||
|             ps.setString(3, message.getFrom().getAddress()); |  | ||||||
|             ps.setString(4, message.getTo() == null ? null : message.getTo().getAddress()); |  | ||||||
|             writeBlob(ps, 5, message); |  | ||||||
|             ps.setBytes(6, message.getAckData()); |  | ||||||
|             ps.setObject(7, message.getSent()); |  | ||||||
|             ps.setObject(8, message.getReceived()); |  | ||||||
|             ps.setString(9, message.getStatus() == null ? null : message.getStatus().name()); |  | ||||||
|             ps.setBytes(10, message.getInitialHash()); |  | ||||||
|             ps.setLong(11, message.getTTL()); |  | ||||||
|             ps.setInt(12, message.getRetries()); |  | ||||||
|             ps.setObject(13, message.getNextTry()); |  | ||||||
|             ps.setObject(14, message.getConversationId()); |  | ||||||
|  |  | ||||||
|             ps.executeUpdate(); |  | ||||||
|             // get generated id |  | ||||||
|             try (ResultSet rs = ps.getGeneratedKeys()) { |  | ||||||
|                 rs.next(); |  | ||||||
|                 message.setId(rs.getLong(1)); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private void update(Connection connection, Plaintext message) throws SQLException, IOException { |  | ||||||
|         try (PreparedStatement ps = connection.prepareStatement( |  | ||||||
|             "UPDATE Message SET iv=?, type=?, sender=?, recipient=?, data=?, ack_data=?, sent=?, received=?, " + |  | ||||||
|                 "status=?, initial_hash=?, ttl=?, retries=?, next_try=? " + |  | ||||||
|                 "WHERE id=?")) { |  | ||||||
|             ps.setBytes(1, message.getInventoryVector() == null ? null : message.getInventoryVector().getHash()); |  | ||||||
|             ps.setString(2, message.getType().name()); |  | ||||||
|             ps.setString(3, message.getFrom().getAddress()); |  | ||||||
|             ps.setString(4, message.getTo() == null ? null : message.getTo().getAddress()); |  | ||||||
|             writeBlob(ps, 5, message); |  | ||||||
|             ps.setBytes(6, message.getAckData()); |  | ||||||
|             ps.setObject(7, message.getSent()); |  | ||||||
|             ps.setObject(8, message.getReceived()); |  | ||||||
|             ps.setString(9, message.getStatus() == null ? null : message.getStatus().name()); |  | ||||||
|             ps.setBytes(10, message.getInitialHash()); |  | ||||||
|             ps.setLong(11, message.getTTL()); |  | ||||||
|             ps.setInt(12, message.getRetries()); |  | ||||||
|             ps.setObject(13, message.getNextTry()); |  | ||||||
|             ps.setLong(14, (Long) message.getId()); |  | ||||||
|             ps.executeUpdate(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void remove(Plaintext message) { |  | ||||||
|         try (Connection connection = config.getConnection()) { |  | ||||||
|             connection.setAutoCommit(false); |  | ||||||
|             try (Statement stmt = connection.createStatement()) { |  | ||||||
|                 stmt.executeUpdate("DELETE FROM Message_Label WHERE message_id = " + message.getId()); |  | ||||||
|                 stmt.executeUpdate("DELETE FROM Message WHERE id = " + message.getId()); |  | ||||||
|                 connection.commit(); |  | ||||||
|             } catch (SQLException e) { |  | ||||||
|                 try { |  | ||||||
|                     connection.rollback(); |  | ||||||
|                 } catch (SQLException e1) { |  | ||||||
|                     LOG.debug(e1.getMessage(), e); |  | ||||||
|                 } |  | ||||||
|                 LOG.error(e.getMessage(), e); |  | ||||||
|             } |  | ||||||
|         } catch (SQLException e) { |  | ||||||
|             LOG.error(e.getMessage(), e); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public List<UUID> findConversations(Label label) { |  | ||||||
|         String where; |  | ||||||
|         if (label == null) { |  | ||||||
|             where = "id NOT IN (SELECT message_id FROM Message_Label)"; |  | ||||||
|         } else { |  | ||||||
|             where = "id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId() + ")"; |  | ||||||
|         } |  | ||||||
|         List<UUID> result = new LinkedList<>(); |  | ||||||
|         try ( |  | ||||||
|             Connection connection = config.getConnection(); |  | ||||||
|             Statement stmt = connection.createStatement(); |  | ||||||
|             ResultSet rs = stmt.executeQuery( |  | ||||||
|                 "SELECT DISTINCT conversation FROM Message WHERE " + where) |  | ||||||
|         ) { |  | ||||||
|             while (rs.next()) { |  | ||||||
|                 result.add((UUID) rs.getObject(1)); |  | ||||||
|             } |  | ||||||
|         } catch (SQLException e) { |  | ||||||
|             LOG.error(e.getMessage(), e); |  | ||||||
|         } |  | ||||||
|         return result; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Replaces every occurrence of the source conversation ID with the target ID |  | ||||||
|      * |  | ||||||
|      * @param source ID of the conversation to be merged |  | ||||||
|      * @param target ID of the merge target |  | ||||||
|      */ |  | ||||||
|     private void mergeConversations(Connection connection, UUID source, UUID target) { |  | ||||||
|         try ( |  | ||||||
|             PreparedStatement ps1 = connection.prepareStatement( |  | ||||||
|                 "UPDATE Message SET conversation=? WHERE conversation=?"); |  | ||||||
|             PreparedStatement ps2 = connection.prepareStatement( |  | ||||||
|                 "UPDATE Message_Parent SET conversation=? WHERE conversation=?") |  | ||||||
|         ) { |  | ||||||
|             ps1.setObject(1, target); |  | ||||||
|             ps1.setObject(2, source); |  | ||||||
|             ps1.executeUpdate(); |  | ||||||
|             ps2.setObject(1, target); |  | ||||||
|             ps2.setObject(2, source); |  | ||||||
|             ps2.executeUpdate(); |  | ||||||
|         } catch (SQLException e) { |  | ||||||
|             LOG.error(e.getMessage(), e); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -0,0 +1,344 @@ | |||||||
|  | /* | ||||||
|  |  * 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.repository | ||||||
|  |  | ||||||
|  | import ch.dissem.bitmessage.entity.Plaintext | ||||||
|  | import ch.dissem.bitmessage.entity.valueobject.InventoryVector | ||||||
|  | import ch.dissem.bitmessage.entity.valueobject.Label | ||||||
|  | import ch.dissem.bitmessage.ports.AbstractMessageRepository | ||||||
|  | import ch.dissem.bitmessage.ports.MessageRepository | ||||||
|  | import ch.dissem.bitmessage.repository.JdbcHelper.writeBlob | ||||||
|  | import org.slf4j.LoggerFactory | ||||||
|  | import java.io.IOException | ||||||
|  | import java.sql.Connection | ||||||
|  | import java.sql.ResultSet | ||||||
|  | import java.sql.SQLException | ||||||
|  | import java.sql.Statement | ||||||
|  | import java.util.* | ||||||
|  |  | ||||||
|  | class JdbcMessageRepository(private val config: JdbcConfig) : AbstractMessageRepository(), MessageRepository { | ||||||
|  |  | ||||||
|  |     override fun findLabels(where: String): List<Label> { | ||||||
|  |         try { | ||||||
|  |             config.connection.use { | ||||||
|  |                 connection -> | ||||||
|  |                 return findLabels(connection, where) | ||||||
|  |             } | ||||||
|  |         } catch (e: SQLException) { | ||||||
|  |             LOG.error(e.message, e) | ||||||
|  |             return ArrayList() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun getLabel(rs: ResultSet): Label { | ||||||
|  |         val typeName = rs.getString("type") | ||||||
|  |         val type = if (typeName == null) { | ||||||
|  |             null | ||||||
|  |         } else { | ||||||
|  |             Label.Type.valueOf(typeName) | ||||||
|  |         } | ||||||
|  |         val label = Label(rs.getString("label"), type, rs.getInt("color")) | ||||||
|  |         label.id = rs.getLong("id") | ||||||
|  |  | ||||||
|  |         return label | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun countUnread(label: Label?): Int { | ||||||
|  |         val where = if (label == null) { | ||||||
|  |             "" | ||||||
|  |         } else { | ||||||
|  |             "id IN (SELECT message_id FROM Message_Label WHERE label_id=${label.id}) AND " | ||||||
|  |         } + "id IN (SELECT message_id FROM Message_Label WHERE label_id IN (" + | ||||||
|  |             "SELECT id FROM Label WHERE type = '" + Label.Type.UNREAD.name + "'))" | ||||||
|  |  | ||||||
|  |         try { | ||||||
|  |             config.connection.use { connection -> | ||||||
|  |                 connection.createStatement().use { stmt -> | ||||||
|  |                     stmt.executeQuery("SELECT count(*) FROM Message WHERE $where").use { rs -> | ||||||
|  |                         if (rs.next()) { | ||||||
|  |                             return rs.getInt(1) | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } catch (e: SQLException) { | ||||||
|  |             LOG.error(e.message, e) | ||||||
|  |         } | ||||||
|  |         return 0 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun find(where: String): List<Plaintext> { | ||||||
|  |         val result = LinkedList<Plaintext>() | ||||||
|  |         try { | ||||||
|  |             config.connection.use { connection -> | ||||||
|  |                 connection.createStatement().use { stmt -> | ||||||
|  |                     stmt.executeQuery( | ||||||
|  |                         """SELECT id, iv, type, sender, recipient, data, ack_data, sent, received, initial_hash, status, ttl, retries, next_try, conversation | ||||||
|  |                            FROM Message WHERE $where""").use { rs -> | ||||||
|  |                         while (rs.next()) { | ||||||
|  |                             val iv = rs.getBytes("iv") | ||||||
|  |                             val data = rs.getBinaryStream("data") | ||||||
|  |                             val type = Plaintext.Type.valueOf(rs.getString("type")) | ||||||
|  |                             val builder = Plaintext.readWithoutSignature(type, data) | ||||||
|  |                             val id = rs.getLong("id") | ||||||
|  |                             builder.id(id) | ||||||
|  |                             builder.IV(InventoryVector.fromHash(iv)) | ||||||
|  |                             builder.from(ctx.addressRepository.getAddress(rs.getString("sender"))!!) | ||||||
|  |                             builder.to(ctx.addressRepository.getAddress(rs.getString("recipient"))) | ||||||
|  |                             builder.ackData(rs.getBytes("ack_data")) | ||||||
|  |                             builder.sent(rs.getObject("sent", Long::class.java)) | ||||||
|  |                             builder.received(rs.getObject("received", Long::class.java)) | ||||||
|  |                             builder.status(Plaintext.Status.valueOf(rs.getString("status"))) | ||||||
|  |                             builder.ttl(rs.getLong("ttl")) | ||||||
|  |                             builder.retries(rs.getInt("retries")) | ||||||
|  |                             builder.nextTry(rs.getObject("next_try", Long::class.java)) | ||||||
|  |                             builder.conversation(rs.getObject("conversation", UUID::class.java)) | ||||||
|  |                             builder.labels(findLabels(connection, | ||||||
|  |                                 "id IN (SELECT label_id FROM Message_Label WHERE message_id=$id) ORDER BY ord")) | ||||||
|  |                             val message = builder.build() | ||||||
|  |                             message.initialHash = rs.getBytes("initial_hash") | ||||||
|  |                             result.add(message) | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } catch (e: SQLException) { | ||||||
|  |             LOG.error(e.message, e) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return result | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun findLabels(connection: Connection, where: String): List<Label> { | ||||||
|  |         val result = ArrayList<Label>() | ||||||
|  |         try { | ||||||
|  |             connection.createStatement().use { stmt -> | ||||||
|  |                 stmt.executeQuery("SELECT id, label, type, color FROM Label WHERE $where").use { rs -> | ||||||
|  |                     while (rs.next()) { | ||||||
|  |                         result.add(getLabel(rs)) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } catch (e: SQLException) { | ||||||
|  |             LOG.error(e.message, e) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return result | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun save(message: Plaintext) { | ||||||
|  |         saveContactIfNecessary(message.from) | ||||||
|  |         saveContactIfNecessary(message.to) | ||||||
|  |  | ||||||
|  |         config.connection.use { connection -> | ||||||
|  |             try { | ||||||
|  |                 connection.autoCommit = false | ||||||
|  |                 save(connection, message) | ||||||
|  |                 updateParents(connection, message) | ||||||
|  |                 updateLabels(connection, message) | ||||||
|  |                 connection.commit() | ||||||
|  |             } catch (e: Exception) { | ||||||
|  |                 connection.rollback() | ||||||
|  |                 throw e | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun save(connection: Connection, message: Plaintext) { | ||||||
|  |         if (message.id == null) { | ||||||
|  |             insert(connection, message) | ||||||
|  |         } else { | ||||||
|  |             update(connection, message) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun updateLabels(connection: Connection, message: Plaintext) { | ||||||
|  |         // remove existing labels | ||||||
|  |         connection.createStatement().use { stmt -> stmt.executeUpdate("DELETE FROM Message_Label WHERE message_id=${message.id!!}") } | ||||||
|  |         // save new labels | ||||||
|  |         connection.prepareStatement("INSERT INTO Message_Label VALUES (${message.id}, ?)").use { ps -> | ||||||
|  |             for (label in message.labels) { | ||||||
|  |                 ps.setLong(1, (label.id as Long?)!!) | ||||||
|  |                 ps.executeUpdate() | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun updateParents(connection: Connection, message: Plaintext) { | ||||||
|  |         if (message.inventoryVector == null || message.parents.isEmpty()) { | ||||||
|  |             // There are no parents to save yet (they are saved in the extended data, that's enough for now) | ||||||
|  |             return | ||||||
|  |         } | ||||||
|  |         // remove existing parents | ||||||
|  |         connection.prepareStatement("DELETE FROM Message_Parent WHERE child=?").use { ps -> | ||||||
|  |             ps.setBytes(1, message.initialHash) | ||||||
|  |             ps.executeUpdate() | ||||||
|  |         } | ||||||
|  |         val childIV = message.inventoryVector!!.hash | ||||||
|  |         // save new parents | ||||||
|  |         var order = 0 | ||||||
|  |         connection.prepareStatement("INSERT INTO Message_Parent VALUES (?, ?, ?, ?)").use { ps -> | ||||||
|  |             for (parentIV in message.parents) { | ||||||
|  |                 val parent = getMessage(parentIV) | ||||||
|  |                 mergeConversations(connection, parent!!.conversationId, message.conversationId) | ||||||
|  |                 order++ | ||||||
|  |                 ps.setBytes(1, parentIV.hash) | ||||||
|  |                 ps.setBytes(2, childIV) | ||||||
|  |                 ps.setInt(3, order) // FIXME: this might not be necessary | ||||||
|  |                 ps.setObject(4, message.conversationId) | ||||||
|  |                 ps.executeUpdate() | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun insert(connection: Connection, message: Plaintext) { | ||||||
|  |         connection.prepareStatement( | ||||||
|  |             "INSERT INTO Message (iv, type, sender, recipient, data, ack_data, sent, received, " + | ||||||
|  |                 "status, initial_hash, ttl, retries, next_try, conversation) " + | ||||||
|  |                 "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", | ||||||
|  |             Statement.RETURN_GENERATED_KEYS).use { ps -> | ||||||
|  |             ps.setBytes(1, if (message.inventoryVector == null) null else message.inventoryVector!!.hash) | ||||||
|  |             ps.setString(2, message.type.name) | ||||||
|  |             ps.setString(3, message.from.address) | ||||||
|  |             ps.setString(4, if (message.to == null) null else message.to!!.address) | ||||||
|  |             writeBlob(ps, 5, message) | ||||||
|  |             ps.setBytes(6, message.ackData) | ||||||
|  |             ps.setObject(7, message.sent) | ||||||
|  |             ps.setObject(8, message.received) | ||||||
|  |             ps.setString(9, message.status.name) | ||||||
|  |             ps.setBytes(10, message.initialHash) | ||||||
|  |             ps.setLong(11, message.ttl) | ||||||
|  |             ps.setInt(12, message.retries) | ||||||
|  |             ps.setObject(13, message.nextTry) | ||||||
|  |             ps.setObject(14, message.conversationId) | ||||||
|  |  | ||||||
|  |             ps.executeUpdate() | ||||||
|  |             // get generated id | ||||||
|  |             ps.generatedKeys.use { rs -> | ||||||
|  |                 rs.next() | ||||||
|  |                 message.id = rs.getLong(1) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Throws(SQLException::class, IOException::class) | ||||||
|  |     private fun update(connection: Connection, message: Plaintext) { | ||||||
|  |         connection.prepareStatement( | ||||||
|  |             "UPDATE Message SET iv=?, type=?, sender=?, recipient=?, data=?, ack_data=?, sent=?, received=?, " + | ||||||
|  |                 "status=?, initial_hash=?, ttl=?, retries=?, next_try=? " + | ||||||
|  |                 "WHERE id=?").use { ps -> | ||||||
|  |             ps.setBytes(1, if (message.inventoryVector == null) null else message.inventoryVector!!.hash) | ||||||
|  |             ps.setString(2, message.type.name) | ||||||
|  |             ps.setString(3, message.from.address) | ||||||
|  |             ps.setString(4, if (message.to == null) null else message.to!!.address) | ||||||
|  |             writeBlob(ps, 5, message) | ||||||
|  |             ps.setBytes(6, message.ackData) | ||||||
|  |             ps.setObject(7, message.sent) | ||||||
|  |             ps.setObject(8, message.received) | ||||||
|  |             ps.setString(9, message.status.name) | ||||||
|  |             ps.setBytes(10, message.initialHash) | ||||||
|  |             ps.setLong(11, message.ttl) | ||||||
|  |             ps.setInt(12, message.retries) | ||||||
|  |             ps.setObject(13, message.nextTry) | ||||||
|  |             ps.setLong(14, (message.id as Long?)!!) | ||||||
|  |             ps.executeUpdate() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun remove(message: Plaintext) { | ||||||
|  |         try { | ||||||
|  |             config.connection.use { connection -> | ||||||
|  |                 connection.autoCommit = false | ||||||
|  |                 try { | ||||||
|  |                     connection.createStatement().use { stmt -> | ||||||
|  |                         stmt.executeUpdate("DELETE FROM Message_Label WHERE message_id = " + message.id!!) | ||||||
|  |                         stmt.executeUpdate("DELETE FROM Message WHERE id = " + message.id!!) | ||||||
|  |                         connection.commit() | ||||||
|  |                     } | ||||||
|  |                 } catch (e: SQLException) { | ||||||
|  |                     try { | ||||||
|  |                         connection.rollback() | ||||||
|  |                     } catch (e1: SQLException) { | ||||||
|  |                         LOG.debug(e1.message, e) | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     LOG.error(e.message, e) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } catch (e: SQLException) { | ||||||
|  |             LOG.error(e.message, e) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun findConversations(label: Label?): List<UUID> { | ||||||
|  |         val where: String | ||||||
|  |         if (label == null) { | ||||||
|  |             where = "id NOT IN (SELECT message_id FROM Message_Label)" | ||||||
|  |         } else { | ||||||
|  |             where = "id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.id + ")" | ||||||
|  |         } | ||||||
|  |         val result = LinkedList<UUID>() | ||||||
|  |         try { | ||||||
|  |             config.connection.use { connection -> | ||||||
|  |                 connection.createStatement().use { stmt -> | ||||||
|  |                     stmt.executeQuery( | ||||||
|  |                         "SELECT DISTINCT conversation FROM Message WHERE " + where).use { rs -> | ||||||
|  |                         while (rs.next()) { | ||||||
|  |                             result.add(rs.getObject(1) as UUID) | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } catch (e: SQLException) { | ||||||
|  |             LOG.error(e.message, e) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return result | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Replaces every occurrence of the source conversation ID with the target ID | ||||||
|  |  | ||||||
|  |      * @param source ID of the conversation to be merged | ||||||
|  |      * * | ||||||
|  |      * @param target ID of the merge target | ||||||
|  |      */ | ||||||
|  |     private fun mergeConversations(connection: Connection, source: UUID, target: UUID) { | ||||||
|  |         try { | ||||||
|  |             connection.prepareStatement( | ||||||
|  |                 "UPDATE Message SET conversation=? WHERE conversation=?").use { ps1 -> | ||||||
|  |                 connection.prepareStatement( | ||||||
|  |                     "UPDATE Message_Parent SET conversation=? WHERE conversation=?").use { ps2 -> | ||||||
|  |                     ps1.setObject(1, target) | ||||||
|  |                     ps1.setObject(2, source) | ||||||
|  |                     ps1.executeUpdate() | ||||||
|  |                     ps2.setObject(1, target) | ||||||
|  |                     ps2.setObject(2, source) | ||||||
|  |                     ps2.executeUpdate() | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } catch (e: SQLException) { | ||||||
|  |             LOG.error(e.message, e) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         private val LOG = LoggerFactory.getLogger(JdbcMessageRepository::class.java) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -105,9 +105,9 @@ public class JdbcProofOfWorkRepository extends JdbcHelper implements ProofOfWork | |||||||
|                 "nonce_trials_per_byte, extra_bytes, expiration_time, message_id) " + |                 "nonce_trials_per_byte, extra_bytes, expiration_time, message_id) " + | ||||||
|                 "VALUES (?, ?, ?, ?, ?, ?, ?)") |                 "VALUES (?, ?, ?, ?, ?, ?, ?)") | ||||||
|         ) { |         ) { | ||||||
|             ps.setBytes(1, cryptography().getInitialHash(item.getObject())); |             ps.setBytes(1, cryptography().getInitialHash(item.getObjectMessage())); | ||||||
|             writeBlob(ps, 2, item.getObject()); |             writeBlob(ps, 2, item.getObjectMessage()); | ||||||
|             ps.setLong(3, item.getObject().getVersion()); |             ps.setLong(3, item.getObjectMessage().getVersion()); | ||||||
|             ps.setLong(4, item.getNonceTrialsPerByte()); |             ps.setLong(4, item.getNonceTrialsPerByte()); | ||||||
|             ps.setLong(5, item.getExtraBytes()); |             ps.setLong(5, item.getExtraBytes()); | ||||||
|  |  | ||||||
| @@ -120,7 +120,7 @@ public class JdbcProofOfWorkRepository extends JdbcHelper implements ProofOfWork | |||||||
|             } |             } | ||||||
|             ps.executeUpdate(); |             ps.executeUpdate(); | ||||||
|         } catch (IOException | SQLException e) { |         } catch (IOException | SQLException e) { | ||||||
|             LOG.debug("Error storing object of type " + item.getObject().getPayload().getClass().getSimpleName(), e); |             LOG.debug("Error storing object of type " + item.getObjectMessage().getPayload().getClass().getSimpleName(), e); | ||||||
|             throw new ApplicationException(e); |             throw new ApplicationException(e); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -129,7 +129,7 @@ class JdbcProofOfWorkRepositoryTest : TestBase() { | |||||||
|     fun `ensure item can be retrieved`() { |     fun `ensure item can be retrieved`() { | ||||||
|         val item = repo.getItem(initialHash1) |         val item = repo.getItem(initialHash1) | ||||||
|         assertThat(item, notNullValue()) |         assertThat(item, notNullValue()) | ||||||
|         assertThat<ObjectPayload>(item.`object`.payload, instanceOf<ObjectPayload>(GetPubkey::class.java)) |         assertThat<ObjectPayload>(item.objectMessage.payload, instanceOf<ObjectPayload>(GetPubkey::class.java)) | ||||||
|         assertThat(item.nonceTrialsPerByte, `is`(1000L)) |         assertThat(item.nonceTrialsPerByte, `is`(1000L)) | ||||||
|         assertThat(item.extraBytes, `is`(1000L)) |         assertThat(item.extraBytes, `is`(1000L)) | ||||||
|     } |     } | ||||||
| @@ -138,7 +138,7 @@ class JdbcProofOfWorkRepositoryTest : TestBase() { | |||||||
|     fun `ensure ack item can be retrieved`() { |     fun `ensure ack item can be retrieved`() { | ||||||
|         val item = repo.getItem(initialHash2) |         val item = repo.getItem(initialHash2) | ||||||
|         assertThat(item, notNullValue()) |         assertThat(item, notNullValue()) | ||||||
|         assertThat<ObjectPayload>(item.`object`.payload, instanceOf<ObjectPayload>(GenericPayload::class.java)) |         assertThat<ObjectPayload>(item.objectMessage.payload, instanceOf<ObjectPayload>(GenericPayload::class.java)) | ||||||
|         assertThat(item.nonceTrialsPerByte, `is`(1000L)) |         assertThat(item.nonceTrialsPerByte, `is`(1000L)) | ||||||
|         assertThat(item.extraBytes, `is`(1000L)) |         assertThat(item.extraBytes, `is`(1000L)) | ||||||
|         assertThat(item.expirationTime, not<Number>(0)) |         assertThat(item.expirationTime, not<Number>(0)) | ||||||
|   | |||||||
| @@ -21,16 +21,11 @@ import ch.dissem.bitmessage.ports.MultiThreadedPOWEngine | |||||||
| import ch.dissem.bitmessage.utils.Singleton | import ch.dissem.bitmessage.utils.Singleton | ||||||
| import ch.dissem.bitmessage.utils.TestUtils.mockedInternalContext | import ch.dissem.bitmessage.utils.TestUtils.mockedInternalContext | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Created by chris on 20.07.15. |  | ||||||
|  */ |  | ||||||
| open class TestBase { | open class TestBase { | ||||||
|     companion object { |     companion object { | ||||||
|         init { |         init { | ||||||
|             val security = BouncyCryptography() |  | ||||||
|             Singleton.initialize(security) |  | ||||||
|             mockedInternalContext( |             mockedInternalContext( | ||||||
|                 cryptography = security, |                 cryptography = BouncyCryptography(), | ||||||
|                 proofOfWorkEngine = MultiThreadedPOWEngine() |                 proofOfWorkEngine = MultiThreadedPOWEngine() | ||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -15,5 +15,6 @@ dependencies { | |||||||
|     compile 'org.ini4j:ini4j:0.5.4' |     compile 'org.ini4j:ini4j:0.5.4' | ||||||
|     testCompile 'junit:junit:4.12' |     testCompile 'junit:junit:4.12' | ||||||
|     testCompile 'org.mockito:mockito-core:2.7.21' |     testCompile 'org.mockito:mockito-core:2.7.21' | ||||||
|  |     testCompile 'com.nhaarman:mockito-kotlin:1.4.0' | ||||||
|     testCompile project(':cryptography-bc') |     testCompile project(':cryptography-bc') | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,106 +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.wif; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.BitmessageContext; |  | ||||||
| import ch.dissem.bitmessage.entity.BitmessageAddress; |  | ||||||
| import ch.dissem.bitmessage.exception.ApplicationException; |  | ||||||
| import ch.dissem.bitmessage.utils.Base58; |  | ||||||
| import org.ini4j.Ini; |  | ||||||
| import org.ini4j.Profile; |  | ||||||
|  |  | ||||||
| import java.io.*; |  | ||||||
| import java.util.Collection; |  | ||||||
|  |  | ||||||
| import static ch.dissem.bitmessage.entity.valueobject.PrivateKey.PRIVATE_KEY_SIZE; |  | ||||||
| import static ch.dissem.bitmessage.utils.Singleton.cryptography; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * @author Christian Basler |  | ||||||
|  */ |  | ||||||
| public class WifExporter { |  | ||||||
|     private final BitmessageContext ctx; |  | ||||||
|     private final Ini ini; |  | ||||||
|  |  | ||||||
|     public WifExporter(BitmessageContext ctx) { |  | ||||||
|         this.ctx = ctx; |  | ||||||
|         this.ini = new Ini(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public WifExporter addAll() { |  | ||||||
|         for (BitmessageAddress identity : ctx.addresses().getIdentities()) { |  | ||||||
|             addIdentity(identity); |  | ||||||
|         } |  | ||||||
|         return this; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public WifExporter addAll(Collection<BitmessageAddress> identities) { |  | ||||||
|         for (BitmessageAddress identity : identities) { |  | ||||||
|             addIdentity(identity); |  | ||||||
|         } |  | ||||||
|         return this; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public WifExporter addIdentity(BitmessageAddress identity) { |  | ||||||
|         Profile.Section section = ini.add(identity.getAddress()); |  | ||||||
|         section.add("label", identity.getAlias()); |  | ||||||
|         section.add("enabled", true); |  | ||||||
|         section.add("decoy", false); |  | ||||||
|         if (identity.isChan()) { |  | ||||||
|             section.add("chan", identity.isChan()); |  | ||||||
|         } |  | ||||||
|         section.add("noncetrialsperbyte", identity.getPubkey().getNonceTrialsPerByte()); |  | ||||||
|         section.add("payloadlengthextrabytes", identity.getPubkey().getExtraBytes()); |  | ||||||
|         section.add("privsigningkey", exportSecret(identity.getPrivateKey().getPrivateSigningKey())); |  | ||||||
|         section.add("privencryptionkey", exportSecret(identity.getPrivateKey().getPrivateEncryptionKey())); |  | ||||||
|         return this; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private String exportSecret(byte[] privateKey) { |  | ||||||
|         if (privateKey.length != PRIVATE_KEY_SIZE) { |  | ||||||
|             throw new IllegalArgumentException("Private key of length 32 expected, but was " + privateKey.length); |  | ||||||
|         } |  | ||||||
|         byte[] result = new byte[37]; |  | ||||||
|         result[0] = (byte) 0x80; |  | ||||||
|         System.arraycopy(privateKey, 0, result, 1, PRIVATE_KEY_SIZE); |  | ||||||
|         byte[] hash = cryptography().doubleSha256(result, PRIVATE_KEY_SIZE + 1); |  | ||||||
|         System.arraycopy(hash, 0, result, PRIVATE_KEY_SIZE + 1, 4); |  | ||||||
|         return Base58.encode(result); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void write(File file) throws IOException { |  | ||||||
|         file.createNewFile(); |  | ||||||
|         try (FileOutputStream out = new FileOutputStream(file)) { |  | ||||||
|             write(out); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void write(OutputStream out) throws IOException { |  | ||||||
|         ini.store(out); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public String toString() { |  | ||||||
|         StringWriter writer = new StringWriter(); |  | ||||||
|         try { |  | ||||||
|             ini.store(writer); |  | ||||||
|         } catch (IOException e) { |  | ||||||
|             throw new ApplicationException(e); |  | ||||||
|         } |  | ||||||
|         return writer.toString(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										103
									
								
								wif/src/main/java/ch/dissem/bitmessage/wif/WifExporter.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								wif/src/main/java/ch/dissem/bitmessage/wif/WifExporter.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright 2017 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. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * 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.wif | ||||||
|  |  | ||||||
|  | import ch.dissem.bitmessage.BitmessageContext | ||||||
|  | import ch.dissem.bitmessage.entity.BitmessageAddress | ||||||
|  | import ch.dissem.bitmessage.entity.valueobject.PrivateKey.Companion.PRIVATE_KEY_SIZE | ||||||
|  | import ch.dissem.bitmessage.utils.Base58 | ||||||
|  | import ch.dissem.bitmessage.utils.Singleton.cryptography | ||||||
|  | import org.ini4j.Ini | ||||||
|  | import java.io.File | ||||||
|  | import java.io.FileOutputStream | ||||||
|  | import java.io.OutputStream | ||||||
|  | import java.io.StringWriter | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @author Christian Basler | ||||||
|  |  */ | ||||||
|  | class WifExporter(private val ctx: BitmessageContext) { | ||||||
|  |     private val ini = Ini() | ||||||
|  |  | ||||||
|  |     fun addAll(): WifExporter { | ||||||
|  |         ctx.addresses.getIdentities().forEach { addIdentity(it) } | ||||||
|  |         return this | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun addAll(identities: Collection<BitmessageAddress>): WifExporter { | ||||||
|  |         identities.forEach { addIdentity(it) } | ||||||
|  |         return this | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun addIdentity(identity: BitmessageAddress): WifExporter { | ||||||
|  |         val section = ini.add(identity.address) | ||||||
|  |         section.add("label", identity.alias) | ||||||
|  |         section.add("enabled", true) | ||||||
|  |         section.add("decoy", false) | ||||||
|  |         if (identity.isChan) { | ||||||
|  |             section.add("chan", identity.isChan) | ||||||
|  |         } | ||||||
|  |         section.add("noncetrialsperbyte", identity.pubkey!!.nonceTrialsPerByte) | ||||||
|  |         section.add("payloadlengthextrabytes", identity.pubkey!!.extraBytes) | ||||||
|  |         section.add("privsigningkey", exportSecret(identity.privateKey!!.privateSigningKey)) | ||||||
|  |         section.add("privencryptionkey", exportSecret(identity.privateKey!!.privateEncryptionKey)) | ||||||
|  |         return this | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun exportSecret(privateKey: ByteArray): String { | ||||||
|  |         if (privateKey.size != PRIVATE_KEY_SIZE) { | ||||||
|  |             throw IllegalArgumentException("Private key of length 32 expected, but was " + privateKey.size) | ||||||
|  |         } | ||||||
|  |         val result = ByteArray(37) | ||||||
|  |         result[0] = 0x80.toByte() | ||||||
|  |         System.arraycopy(privateKey, 0, result, 1, PRIVATE_KEY_SIZE) | ||||||
|  |         val hash = cryptography().doubleSha256(result, PRIVATE_KEY_SIZE + 1) | ||||||
|  |         System.arraycopy(hash, 0, result, PRIVATE_KEY_SIZE + 1, 4) | ||||||
|  |         return Base58.encode(result) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun write(file: File) { | ||||||
|  |         file.createNewFile() | ||||||
|  |         FileOutputStream(file).use { out -> write(out) } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun write(out: OutputStream) { | ||||||
|  |         ini.store(out) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun toString(): String { | ||||||
|  |         val writer = StringWriter() | ||||||
|  |         ini.store(writer) | ||||||
|  |         return writer.toString() | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,119 +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.wif; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.BitmessageContext; |  | ||||||
| import ch.dissem.bitmessage.entity.BitmessageAddress; |  | ||||||
| import ch.dissem.bitmessage.entity.payload.Pubkey; |  | ||||||
| import ch.dissem.bitmessage.factory.Factory; |  | ||||||
| import ch.dissem.bitmessage.utils.Base58; |  | ||||||
| import org.ini4j.Ini; |  | ||||||
| import org.ini4j.Profile; |  | ||||||
|  |  | ||||||
| import java.io.*; |  | ||||||
| import java.util.Arrays; |  | ||||||
| import java.util.Collection; |  | ||||||
| import java.util.LinkedList; |  | ||||||
| import java.util.List; |  | ||||||
| import java.util.Map.Entry; |  | ||||||
|  |  | ||||||
| import static ch.dissem.bitmessage.utils.Singleton.cryptography; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * @author Christian Basler |  | ||||||
|  */ |  | ||||||
| public class WifImporter { |  | ||||||
|     private static final byte WIF_FIRST_BYTE = (byte) 0x80; |  | ||||||
|     private static final int WIF_SECRET_LENGTH = 37; |  | ||||||
|  |  | ||||||
|     private final BitmessageContext ctx; |  | ||||||
|     private final List<BitmessageAddress> identities = new LinkedList<>(); |  | ||||||
|  |  | ||||||
|     public WifImporter(BitmessageContext ctx, File file) throws IOException { |  | ||||||
|         this(ctx, new FileInputStream(file)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public WifImporter(BitmessageContext ctx, String data) throws IOException { |  | ||||||
|         this(ctx, new ByteArrayInputStream(data.getBytes("utf-8"))); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public WifImporter(BitmessageContext ctx, InputStream in, Pubkey.Feature... features) throws IOException { |  | ||||||
|         this.ctx = ctx; |  | ||||||
|  |  | ||||||
|         Ini ini = new Ini(); |  | ||||||
|         ini.load(in); |  | ||||||
|  |  | ||||||
|         for (Entry<String, Profile.Section> entry : ini.entrySet()) { |  | ||||||
|             if (!entry.getKey().startsWith("BM-")) |  | ||||||
|                 continue; |  | ||||||
|  |  | ||||||
|             Profile.Section section = entry.getValue(); |  | ||||||
|             BitmessageAddress address = Factory.createIdentityFromPrivateKey( |  | ||||||
|                 entry.getKey(), |  | ||||||
|                 getSecret(section.get("privsigningkey")), |  | ||||||
|                 getSecret(section.get("privencryptionkey")), |  | ||||||
|                 Long.parseLong(section.get("noncetrialsperbyte")), |  | ||||||
|                 Long.parseLong(section.get("payloadlengthextrabytes")), |  | ||||||
|                 Pubkey.Feature.bitfield(features) |  | ||||||
|             ); |  | ||||||
|             if (section.containsKey("chan")) { |  | ||||||
|                 address.setChan(Boolean.parseBoolean(section.get("chan"))); |  | ||||||
|             } |  | ||||||
|             address.setAlias(section.get("label")); |  | ||||||
|             identities.add(address); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private byte[] getSecret(String walletImportFormat) throws IOException { |  | ||||||
|         byte[] bytes = Base58.decode(walletImportFormat); |  | ||||||
|         if (bytes[0] != WIF_FIRST_BYTE) |  | ||||||
|             throw new IOException("Unknown format: 0x80 expected as first byte, but secret " + walletImportFormat + |  | ||||||
|                 " was " + bytes[0]); |  | ||||||
|         if (bytes.length != WIF_SECRET_LENGTH) |  | ||||||
|             throw new IOException("Unknown format: " + WIF_SECRET_LENGTH + |  | ||||||
|                 " bytes expected, but secret " + walletImportFormat + " was " + bytes.length + " long"); |  | ||||||
|  |  | ||||||
|         byte[] hash = cryptography().doubleSha256(bytes, 33); |  | ||||||
|         for (int i = 0; i < 4; i++) { |  | ||||||
|             if (hash[i] != bytes[33 + i]) throw new IOException("Hash check failed for secret " + walletImportFormat); |  | ||||||
|         } |  | ||||||
|         return Arrays.copyOfRange(bytes, 1, 33); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public List<BitmessageAddress> getIdentities() { |  | ||||||
|         return identities; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public WifImporter importAll() { |  | ||||||
|         for (BitmessageAddress identity : identities) { |  | ||||||
|             ctx.addresses().save(identity); |  | ||||||
|         } |  | ||||||
|         return this; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public WifImporter importAll(Collection<BitmessageAddress> identities) { |  | ||||||
|         for (BitmessageAddress identity : identities) { |  | ||||||
|             ctx.addresses().save(identity); |  | ||||||
|         } |  | ||||||
|         return this; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public WifImporter importIdentity(BitmessageAddress identity) { |  | ||||||
|         ctx.addresses().save(identity); |  | ||||||
|         return this; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										126
									
								
								wif/src/main/java/ch/dissem/bitmessage/wif/WifImporter.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								wif/src/main/java/ch/dissem/bitmessage/wif/WifImporter.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,126 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright 2017 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. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * 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.wif | ||||||
|  |  | ||||||
|  | import ch.dissem.bitmessage.BitmessageContext | ||||||
|  | import ch.dissem.bitmessage.entity.BitmessageAddress | ||||||
|  | import ch.dissem.bitmessage.entity.payload.Pubkey | ||||||
|  | import ch.dissem.bitmessage.exception.ApplicationException | ||||||
|  | import ch.dissem.bitmessage.factory.Factory | ||||||
|  | import ch.dissem.bitmessage.utils.Base58 | ||||||
|  | import ch.dissem.bitmessage.utils.Singleton.cryptography | ||||||
|  | import org.ini4j.Ini | ||||||
|  | import java.io.ByteArrayInputStream | ||||||
|  | import java.io.File | ||||||
|  | import java.io.FileInputStream | ||||||
|  | import java.io.InputStream | ||||||
|  | import java.util.* | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @author Christian Basler | ||||||
|  |  */ | ||||||
|  | class WifImporter constructor( | ||||||
|  |     private val ctx: BitmessageContext, | ||||||
|  |     `in`: InputStream, | ||||||
|  |     vararg features: Pubkey.Feature | ||||||
|  | ) { | ||||||
|  |     private val identities = LinkedList<BitmessageAddress>() | ||||||
|  |  | ||||||
|  |     constructor(ctx: BitmessageContext, file: File) : this(ctx, FileInputStream(file)) | ||||||
|  |  | ||||||
|  |     constructor(ctx: BitmessageContext, data: String) : this(ctx, ByteArrayInputStream(data.toByteArray(charset("utf-8")))) | ||||||
|  |  | ||||||
|  |     init { | ||||||
|  |         val ini = Ini() | ||||||
|  |         ini.load(`in`) | ||||||
|  |  | ||||||
|  |         for ((key, section) in ini) { | ||||||
|  |             if (!key.startsWith("BM-")) | ||||||
|  |                 continue | ||||||
|  |  | ||||||
|  |             val address = Factory.createIdentityFromPrivateKey( | ||||||
|  |                 key, | ||||||
|  |                 getSecret(section["privsigningkey"] ?: throw ApplicationException("privsigningkey missing for $key")), | ||||||
|  |                 getSecret(section["privencryptionkey"] ?: throw ApplicationException("privencryptionkey missing for $key")), | ||||||
|  |                 section["noncetrialsperbyte"]?.toLongOrNull() ?: throw ApplicationException("noncetrialsperbyte missing for $key"), | ||||||
|  |                 section["payloadlengthextrabytes"]?.toLongOrNull() ?: throw ApplicationException("payloadlengthextrabytes missing for $key"), | ||||||
|  |                 Pubkey.Feature.bitfield(*features) | ||||||
|  |             ) | ||||||
|  |             if (section.containsKey("chan")) { | ||||||
|  |                 address.isChan = java.lang.Boolean.parseBoolean(section["chan"]) | ||||||
|  |             } | ||||||
|  |             address.alias = section["label"] | ||||||
|  |             identities.add(address) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun getSecret(walletImportFormat: String): ByteArray { | ||||||
|  |         val bytes = Base58.decode(walletImportFormat) | ||||||
|  |         if (bytes[0] != WIF_FIRST_BYTE) | ||||||
|  |             throw ApplicationException("Unknown format: 0x80 expected as first byte, but secret " + walletImportFormat + | ||||||
|  |                 " was " + bytes[0]) | ||||||
|  |         if (bytes.size != WIF_SECRET_LENGTH) | ||||||
|  |             throw ApplicationException("Unknown format: " + WIF_SECRET_LENGTH + | ||||||
|  |                 " bytes expected, but secret " + walletImportFormat + " was " + bytes.size + " long") | ||||||
|  |  | ||||||
|  |         val hash = cryptography().doubleSha256(bytes, 33) | ||||||
|  |         (0..3) | ||||||
|  |             .filter { hash[it] != bytes[33 + it] } | ||||||
|  |             .forEach { throw ApplicationException("Hash check failed for secret " + walletImportFormat) } | ||||||
|  |         return Arrays.copyOfRange(bytes, 1, 33) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun getIdentities(): List<BitmessageAddress> { | ||||||
|  |         return identities | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun importAll(): WifImporter { | ||||||
|  |         identities.forEach { ctx.addresses.save(it) } | ||||||
|  |         return this | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun importAll(identities: Collection<BitmessageAddress>): WifImporter { | ||||||
|  |         identities.forEach { ctx.addresses.save(it) } | ||||||
|  |         return this | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun importIdentity(identity: BitmessageAddress): WifImporter { | ||||||
|  |         ctx.addresses.save(identity) | ||||||
|  |         return this | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         private const val WIF_FIRST_BYTE = 0x80.toByte() | ||||||
|  |         private const val WIF_SECRET_LENGTH = 37 | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,107 +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.wif; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.BitmessageContext; |  | ||||||
| import ch.dissem.bitmessage.entity.BitmessageAddress; |  | ||||||
| import ch.dissem.bitmessage.ports.*; |  | ||||||
| import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography; |  | ||||||
| import org.junit.Before; |  | ||||||
| import org.junit.Test; |  | ||||||
|  |  | ||||||
| import static org.junit.Assert.assertEquals; |  | ||||||
| import static org.mockito.Mockito.mock; |  | ||||||
| import static org.mockito.Mockito.when; |  | ||||||
|  |  | ||||||
| public class WifExporterTest { |  | ||||||
|     private AddressRepository repo = mock(AddressRepository.class); |  | ||||||
|     private BitmessageContext ctx; |  | ||||||
|     private WifImporter importer; |  | ||||||
|     private WifExporter exporter; |  | ||||||
|  |  | ||||||
|     @Before |  | ||||||
|     public void setUp() throws Exception { |  | ||||||
|         ctx = new BitmessageContext.Builder() |  | ||||||
|                 .cryptography(BouncyCryptography.INSTANCE) |  | ||||||
|                 .networkHandler(mock(NetworkHandler.class)) |  | ||||||
|                 .inventory(mock(Inventory.class)) |  | ||||||
|                 .messageRepo(mock(MessageRepository.class)) |  | ||||||
|                 .powRepo(mock(ProofOfWorkRepository.class)) |  | ||||||
|                 .nodeRegistry(mock(NodeRegistry.class)) |  | ||||||
|                 .addressRepo(repo) |  | ||||||
|                 .build(); |  | ||||||
|         importer = new WifImporter(ctx, getClass().getClassLoader().getResourceAsStream("nuked.dat")); |  | ||||||
|         assertEquals(81, importer.getIdentities().size()); |  | ||||||
|         exporter = new WifExporter(ctx); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Test |  | ||||||
|     public void testAddAll() throws Exception { |  | ||||||
|         when(repo.getIdentities()).thenReturn(importer.getIdentities()); |  | ||||||
|         exporter.addAll(); |  | ||||||
|         String result = exporter.toString(); |  | ||||||
|         int count = 0; |  | ||||||
|         for (int i = 0; i < result.length(); i++) { |  | ||||||
|             if (result.charAt(i) == '[') count++; |  | ||||||
|         } |  | ||||||
|         assertEquals(importer.getIdentities().size(), count); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Test |  | ||||||
|     public void testAddAllFromCollection() throws Exception { |  | ||||||
|         exporter.addAll(importer.getIdentities()); |  | ||||||
|         String result = exporter.toString(); |  | ||||||
|         int count = 0; |  | ||||||
|         for (int i = 0; i < result.length(); i++) { |  | ||||||
|             if (result.charAt(i) == '[') count++; |  | ||||||
|         } |  | ||||||
|         assertEquals(importer.getIdentities().size(), count); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Test |  | ||||||
|     public void testAddIdentity() throws Exception { |  | ||||||
|         String expected = "[BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn]" + System.lineSeparator() + |  | ||||||
|                 "label = Nuked Address" + System.lineSeparator() + |  | ||||||
|                 "enabled = true" + System.lineSeparator() + |  | ||||||
|                 "decoy = false" + System.lineSeparator() + |  | ||||||
|                 "noncetrialsperbyte = 320" + System.lineSeparator() + |  | ||||||
|                 "payloadlengthextrabytes = 14000" + System.lineSeparator() + |  | ||||||
|                 "privsigningkey = 5KU2gbe9u4rKJ8PHYb1rvwMnZnAJj4gtV5GLwoYckeYzygWUzB9" + System.lineSeparator() + |  | ||||||
|                 "privencryptionkey = 5KHd4c6cavd8xv4kzo3PwnVaYuBgEfg7voPQ5V97aZKgpYBXGck" + System.lineSeparator() + |  | ||||||
|                 System.lineSeparator(); |  | ||||||
|         importer = new WifImporter(ctx, expected); |  | ||||||
|         exporter.addIdentity(importer.getIdentities().get(0)); |  | ||||||
|         assertEquals(expected, exporter.toString()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Test |  | ||||||
|     public void ensureChanIsAdded() throws Exception { |  | ||||||
|         String expected = "[BM-2cW67GEKkHGonXKZLCzouLLxnLym3azS8r]" + System.lineSeparator() + |  | ||||||
|                 "label = general" + System.lineSeparator() + |  | ||||||
|                 "enabled = true" + System.lineSeparator() + |  | ||||||
|                 "decoy = false" + System.lineSeparator() + |  | ||||||
|                 "chan = true" + System.lineSeparator() + |  | ||||||
|                 "noncetrialsperbyte = 1000" + System.lineSeparator() + |  | ||||||
|                 "payloadlengthextrabytes = 1000" + System.lineSeparator() + |  | ||||||
|                 "privsigningkey = 5Jnbdwc4u4DG9ipJxYLznXSvemkRFueQJNHujAQamtDDoX3N1eQ" + System.lineSeparator() + |  | ||||||
|                 "privencryptionkey = 5JrDcFtQDv5ydcHRW6dfGUEvThoxCCLNEUaxQfy8LXXgTJzVAcq" + System.lineSeparator() + |  | ||||||
|                 System.lineSeparator(); |  | ||||||
|         BitmessageAddress chan = ctx.joinChan("general", "BM-2cW67GEKkHGonXKZLCzouLLxnLym3azS8r"); |  | ||||||
|         exporter.addIdentity(chan); |  | ||||||
|         assertEquals(expected, exporter.toString()); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										122
									
								
								wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,122 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright 2017 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. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * 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.wif | ||||||
|  |  | ||||||
|  | import ch.dissem.bitmessage.BitmessageContext | ||||||
|  | import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography | ||||||
|  | import ch.dissem.bitmessage.ports.AddressRepository | ||||||
|  | import com.nhaarman.mockito_kotlin.mock | ||||||
|  | import com.nhaarman.mockito_kotlin.whenever | ||||||
|  | import org.junit.Assert.assertEquals | ||||||
|  | import org.junit.Before | ||||||
|  | import org.junit.Test | ||||||
|  |  | ||||||
|  | class WifExporterTest { | ||||||
|  |     private val repo = mock<AddressRepository>() | ||||||
|  |     private lateinit var ctx: BitmessageContext | ||||||
|  |     private lateinit var importer: WifImporter | ||||||
|  |     private lateinit var exporter: WifExporter | ||||||
|  |  | ||||||
|  |     @Before | ||||||
|  |     fun setUp() { | ||||||
|  |         ctx = BitmessageContext.Builder() | ||||||
|  |             .cryptography(BouncyCryptography()) | ||||||
|  |             .networkHandler(mock()) | ||||||
|  |             .inventory(mock()) | ||||||
|  |             .messageRepo(mock()) | ||||||
|  |             .powRepo(mock()) | ||||||
|  |             .nodeRegistry(mock()) | ||||||
|  |             .addressRepo(repo) | ||||||
|  |             .listener { } | ||||||
|  |             .build() | ||||||
|  |         importer = WifImporter(ctx, javaClass.classLoader.getResourceAsStream("nuked.dat")) | ||||||
|  |         assertEquals(81, importer.getIdentities().size) | ||||||
|  |         exporter = WifExporter(ctx) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     fun `ensure all identities in context are added`() { | ||||||
|  |         whenever(repo.getIdentities()).thenReturn(importer.getIdentities()) | ||||||
|  |         exporter.addAll() | ||||||
|  |         val result = exporter.toString() | ||||||
|  |         var count = 0 | ||||||
|  |         for (i in 0..result.length - 1) { | ||||||
|  |             if (result[i] == '[') count++ | ||||||
|  |         } | ||||||
|  |         assertEquals(importer.getIdentities().size, count) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     fun `ensure all from a collection are added`() { | ||||||
|  |         exporter.addAll(importer.getIdentities()) | ||||||
|  |         val result = exporter.toString() | ||||||
|  |         var count = 0 | ||||||
|  |         for (i in 0..result.length - 1) { | ||||||
|  |             if (result[i] == '[') count++ | ||||||
|  |         } | ||||||
|  |         assertEquals(importer.getIdentities().size, count) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     fun `ensure identity is added`() { | ||||||
|  |         val expected = "[BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn]" + System.lineSeparator() + | ||||||
|  |             "label = Nuked Address" + System.lineSeparator() + | ||||||
|  |             "enabled = true" + System.lineSeparator() + | ||||||
|  |             "decoy = false" + System.lineSeparator() + | ||||||
|  |             "noncetrialsperbyte = 320" + System.lineSeparator() + | ||||||
|  |             "payloadlengthextrabytes = 14000" + System.lineSeparator() + | ||||||
|  |             "privsigningkey = 5KU2gbe9u4rKJ8PHYb1rvwMnZnAJj4gtV5GLwoYckeYzygWUzB9" + System.lineSeparator() + | ||||||
|  |             "privencryptionkey = 5KHd4c6cavd8xv4kzo3PwnVaYuBgEfg7voPQ5V97aZKgpYBXGck" + System.lineSeparator() + | ||||||
|  |             System.lineSeparator() | ||||||
|  |         importer = WifImporter(ctx, expected) | ||||||
|  |         exporter.addIdentity(importer.getIdentities()[0]) | ||||||
|  |         assertEquals(expected, exporter.toString()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     fun `ensure chan is added`() { | ||||||
|  |         val expected = "[BM-2cW67GEKkHGonXKZLCzouLLxnLym3azS8r]" + System.lineSeparator() + | ||||||
|  |             "label = general" + System.lineSeparator() + | ||||||
|  |             "enabled = true" + System.lineSeparator() + | ||||||
|  |             "decoy = false" + System.lineSeparator() + | ||||||
|  |             "chan = true" + System.lineSeparator() + | ||||||
|  |             "noncetrialsperbyte = 1000" + System.lineSeparator() + | ||||||
|  |             "payloadlengthextrabytes = 1000" + System.lineSeparator() + | ||||||
|  |             "privsigningkey = 5Jnbdwc4u4DG9ipJxYLznXSvemkRFueQJNHujAQamtDDoX3N1eQ" + System.lineSeparator() + | ||||||
|  |             "privencryptionkey = 5JrDcFtQDv5ydcHRW6dfGUEvThoxCCLNEUaxQfy8LXXgTJzVAcq" + System.lineSeparator() + | ||||||
|  |             System.lineSeparator() | ||||||
|  |         val chan = ctx.joinChan("general", "BM-2cW67GEKkHGonXKZLCzouLLxnLym3azS8r") | ||||||
|  |         exporter.addIdentity(chan) | ||||||
|  |         assertEquals(expected, exporter.toString()) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,116 +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.wif; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.BitmessageContext; |  | ||||||
| import ch.dissem.bitmessage.entity.BitmessageAddress; |  | ||||||
| import ch.dissem.bitmessage.ports.*; |  | ||||||
| import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography; |  | ||||||
| import org.junit.Before; |  | ||||||
| import org.junit.Test; |  | ||||||
|  |  | ||||||
| import java.util.List; |  | ||||||
|  |  | ||||||
| import static org.junit.Assert.*; |  | ||||||
| import static org.mockito.Mockito.*; |  | ||||||
|  |  | ||||||
| public class WifImporterTest { |  | ||||||
|     private AddressRepository repo = mock(AddressRepository.class); |  | ||||||
|     private BitmessageContext ctx; |  | ||||||
|     private WifImporter importer; |  | ||||||
|  |  | ||||||
|     @Before |  | ||||||
|     public void setUp() throws Exception { |  | ||||||
|         ctx = new BitmessageContext.Builder() |  | ||||||
|                 .cryptography(BouncyCryptography.INSTANCE) |  | ||||||
|                 .networkHandler(mock(NetworkHandler.class)) |  | ||||||
|                 .inventory(mock(Inventory.class)) |  | ||||||
|                 .messageRepo(mock(MessageRepository.class)) |  | ||||||
|                 .powRepo(mock(ProofOfWorkRepository.class)) |  | ||||||
|                 .nodeRegistry(mock(NodeRegistry.class)) |  | ||||||
|                 .addressRepo(repo) |  | ||||||
|                 .build(); |  | ||||||
|         importer = new WifImporter(ctx, getClass().getClassLoader().getResourceAsStream("nuked.dat")); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     @Test |  | ||||||
|     public void testImportSingleIdentity() throws Exception { |  | ||||||
|         importer = new WifImporter(ctx, "[BM-2cWJ4UFRTCehWuWNsW8fJkAYMxU4S8jxci]\n" + |  | ||||||
|                 "label = Nuked Address\n" + |  | ||||||
|                 "enabled = true\n" + |  | ||||||
|                 "decoy = false\n" + |  | ||||||
|                 "noncetrialsperbyte = 320\n" + |  | ||||||
|                 "payloadlengthextrabytes = 14000\n" + |  | ||||||
|                 "privsigningkey = 5JU5t2JA58sP5aJwKAcrYg5EpBA9bJPrBSaFfaZ7ogmwTMDCfHL\n" + |  | ||||||
|                 "privencryptionkey = 5Kkx5MwjQcM4kyduKvCEPM6nVNynMdRcg88VQ5iVDWUekMz1igH"); |  | ||||||
|         assertEquals(1, importer.getIdentities().size()); |  | ||||||
|         BitmessageAddress identity = importer.getIdentities().get(0); |  | ||||||
|         assertEquals("BM-2cWJ4UFRTCehWuWNsW8fJkAYMxU4S8jxci", identity.getAddress()); |  | ||||||
|         assertEquals("Nuked Address", identity.getAlias()); |  | ||||||
|         assertEquals(320, identity.getPubkey().getNonceTrialsPerByte()); |  | ||||||
|         assertEquals(14000, identity.getPubkey().getExtraBytes()); |  | ||||||
|         assertNotNull("Private key", identity.getPrivateKey()); |  | ||||||
|         assertEquals(32, identity.getPrivateKey().getPrivateEncryptionKey().length); |  | ||||||
|         assertEquals(32, identity.getPrivateKey().getPrivateSigningKey().length); |  | ||||||
|         assertFalse(identity.isChan()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Test |  | ||||||
|     public void testGetIdentities() throws Exception { |  | ||||||
|         List<BitmessageAddress> identities = importer.getIdentities(); |  | ||||||
|         assertEquals(81, identities.size()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Test |  | ||||||
|     public void testImportAll() throws Exception { |  | ||||||
|         importer.importAll(); |  | ||||||
|         verify(repo, times(81)).save(any(BitmessageAddress.class)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Test |  | ||||||
|     public void testImportAllFromCollection() throws Exception { |  | ||||||
|         List<BitmessageAddress> identities = importer.getIdentities(); |  | ||||||
|         importer.importAll(identities); |  | ||||||
|         for (BitmessageAddress identity : identities) { |  | ||||||
|             verify(repo, times(1)).save(identity); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Test |  | ||||||
|     public void testImportIdentity() throws Exception { |  | ||||||
|         List<BitmessageAddress> identities = importer.getIdentities(); |  | ||||||
|         importer.importIdentity(identities.get(0)); |  | ||||||
|         verify(repo, times(1)).save(identities.get(0)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Test |  | ||||||
|     public void ensureChanIsImported() throws Exception { |  | ||||||
|         importer = new WifImporter(ctx, "[BM-2cW67GEKkHGonXKZLCzouLLxnLym3azS8r]\n" + |  | ||||||
|                 "label = [chan] general\n" + |  | ||||||
|                 "enabled = true\n" + |  | ||||||
|                 "decoy = false\n" + |  | ||||||
|                 "chan = true\n" + |  | ||||||
|                 "noncetrialsperbyte = 1000\n" + |  | ||||||
|                 "payloadlengthextrabytes = 1000\n" + |  | ||||||
|                 "privsigningkey = 5Jnbdwc4u4DG9ipJxYLznXSvemkRFueQJNHujAQamtDDoX3N1eQ\n" + |  | ||||||
|                 "privencryptionkey = 5JrDcFtQDv5ydcHRW6dfGUEvThoxCCLNEUaxQfy8LXXgTJzVAcq\n"); |  | ||||||
|         assertEquals(1, importer.getIdentities().size()); |  | ||||||
|         BitmessageAddress chan = importer.getIdentities().get(0); |  | ||||||
|         assertTrue(chan.isChan()); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										132
									
								
								wif/src/test/java/ch/dissem/bitmessage/wif/WifImporterTest.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								wif/src/test/java/ch/dissem/bitmessage/wif/WifImporterTest.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,132 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright 2017 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. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * 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.wif | ||||||
|  |  | ||||||
|  | import ch.dissem.bitmessage.BitmessageContext | ||||||
|  | import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography | ||||||
|  | import ch.dissem.bitmessage.ports.AddressRepository | ||||||
|  | import com.nhaarman.mockito_kotlin.any | ||||||
|  | import com.nhaarman.mockito_kotlin.mock | ||||||
|  | import com.nhaarman.mockito_kotlin.times | ||||||
|  | import com.nhaarman.mockito_kotlin.verify | ||||||
|  | import org.junit.Assert.* | ||||||
|  | import org.junit.Before | ||||||
|  | import org.junit.Test | ||||||
|  |  | ||||||
|  | class WifImporterTest { | ||||||
|  |     private val repo = mock<AddressRepository>() | ||||||
|  |     private lateinit var ctx: BitmessageContext | ||||||
|  |     private lateinit var importer: WifImporter | ||||||
|  |  | ||||||
|  |     @Before | ||||||
|  |     fun setUp() { | ||||||
|  |         ctx = BitmessageContext.Builder() | ||||||
|  |             .cryptography(BouncyCryptography()) | ||||||
|  |             .networkHandler(mock()) | ||||||
|  |             .inventory(mock()) | ||||||
|  |             .messageRepo(mock()) | ||||||
|  |             .powRepo(mock()) | ||||||
|  |             .nodeRegistry(mock()) | ||||||
|  |             .addressRepo(repo) | ||||||
|  |             .listener { } | ||||||
|  |             .build() | ||||||
|  |         importer = WifImporter(ctx, javaClass.classLoader.getResourceAsStream("nuked.dat")) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     fun `ensure single identity is imported`() { | ||||||
|  |         importer = WifImporter(ctx, "[BM-2cWJ4UFRTCehWuWNsW8fJkAYMxU4S8jxci]\n" + | ||||||
|  |             "label = Nuked Address\n" + | ||||||
|  |             "enabled = true\n" + | ||||||
|  |             "decoy = false\n" + | ||||||
|  |             "noncetrialsperbyte = 320\n" + | ||||||
|  |             "payloadlengthextrabytes = 14000\n" + | ||||||
|  |             "privsigningkey = 5JU5t2JA58sP5aJwKAcrYg5EpBA9bJPrBSaFfaZ7ogmwTMDCfHL\n" + | ||||||
|  |             "privencryptionkey = 5Kkx5MwjQcM4kyduKvCEPM6nVNynMdRcg88VQ5iVDWUekMz1igH") | ||||||
|  |         assertEquals(1, importer.getIdentities().size) | ||||||
|  |         val identity = importer.getIdentities()[0] | ||||||
|  |         assertEquals("BM-2cWJ4UFRTCehWuWNsW8fJkAYMxU4S8jxci", identity.address) | ||||||
|  |         assertEquals("Nuked Address", identity.alias) | ||||||
|  |         assertEquals(320L, identity.pubkey?.nonceTrialsPerByte) | ||||||
|  |         assertEquals(14000L, identity.pubkey?.extraBytes) | ||||||
|  |         assertNotNull("Private key", identity.privateKey) | ||||||
|  |         assertEquals(32, identity.privateKey?.privateEncryptionKey?.size) | ||||||
|  |         assertEquals(32, identity.privateKey?.privateSigningKey?.size) | ||||||
|  |         assertFalse(identity.isChan) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     fun `ensure all identities are retrieved`() { | ||||||
|  |         val identities = importer.getIdentities() | ||||||
|  |         assertEquals(81, identities.size) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     fun `ensure all identities are imported`() { | ||||||
|  |         importer.importAll() | ||||||
|  |         verify(repo, times(81)).save(any()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     fun `ensure all identities in collection are imported`() { | ||||||
|  |         val identities = importer.getIdentities() | ||||||
|  |         importer.importAll(identities) | ||||||
|  |         for (identity in identities) { | ||||||
|  |             verify(repo, times(1)).save(identity) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     fun `ensure single identity from list is imported`() { | ||||||
|  |         val identities = importer.getIdentities() | ||||||
|  |         importer.importIdentity(identities[0]) | ||||||
|  |         verify(repo, times(1)).save(identities[0]) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     fun `ensure chan is imported`() { | ||||||
|  |         importer = WifImporter(ctx, "[BM-2cW67GEKkHGonXKZLCzouLLxnLym3azS8r]\n" + | ||||||
|  |             "label = [chan] general\n" + | ||||||
|  |             "enabled = true\n" + | ||||||
|  |             "decoy = false\n" + | ||||||
|  |             "chan = true\n" + | ||||||
|  |             "noncetrialsperbyte = 1000\n" + | ||||||
|  |             "payloadlengthextrabytes = 1000\n" + | ||||||
|  |             "privsigningkey = 5Jnbdwc4u4DG9ipJxYLznXSvemkRFueQJNHujAQamtDDoX3N1eQ\n" + | ||||||
|  |             "privencryptionkey = 5JrDcFtQDv5ydcHRW6dfGUEvThoxCCLNEUaxQfy8LXXgTJzVAcq\n") | ||||||
|  |         assertEquals(1, importer.getIdentities().size) | ||||||
|  |         val chan = importer.getIdentities()[0] | ||||||
|  |         assertTrue(chan.isChan) | ||||||
|  |     } | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user