Fixes and improvements, SystemTest still broken

This commit is contained in:
Christian Basler 2017-06-16 07:03:12 +02:00
parent 1d3340a547
commit 894e0ff724
62 changed files with 1364 additions and 1276 deletions

View File

@ -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();
``` ```

View File

@ -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 {

View File

@ -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 {

View File

@ -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")
} }
} }

View File

@ -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
}
} }
} }

View File

@ -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)
} }

View File

@ -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)
} }

View File

@ -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)
} }

View File

@ -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)
} }

View File

@ -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))

View File

@ -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
@ -42,6 +43,16 @@ fun message(encoding: Plaintext.Encoding, subject: String, body: String): ByteAr
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)
} }
} }
} }

View File

@ -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)

View File

@ -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)

View File

@ -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)
} }

View File

@ -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

View File

@ -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 {

View File

@ -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)
} }

View File

@ -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(

View File

@ -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
} }
} }
} }

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -81,6 +81,6 @@ interface NetworkHandler {
interface MessageListener { interface MessageListener {
@Throws(IOException::class) @Throws(IOException::class)
fun receive(`object`: ObjectMessage) fun receive(objectMessage: ObjectMessage)
} }
} }

View File

@ -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

View File

@ -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

View File

@ -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) {

View File

@ -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) {

View File

@ -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))

View File

@ -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))
} }
} }

View File

@ -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!!))
} }
} }

View File

@ -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) {

View File

@ -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

View File

@ -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() {

View File

@ -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)
} }
} }

View File

@ -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() }

View File

@ -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])
} }
} }
} }

View File

@ -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
} }

View File

@ -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")
}
} }

View File

@ -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)

View File

@ -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")
}
} }

View File

@ -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)

View File

@ -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() {

View File

@ -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);
} }

View File

@ -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
} }

View File

@ -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;
} }
} }

View File

@ -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();
} }

View File

@ -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

View File

@ -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);
}
}
}

View File

@ -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)
}
}

View File

@ -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);
} }
} }

View File

@ -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))

View File

@ -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()
) )
} }

View File

@ -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')
} }

View File

@ -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();
}
}

View 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()
}
}

View File

@ -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;
}
}

View 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
}
}

View File

@ -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());
}
}

View 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())
}
}

View File

@ -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());
}
}

View 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)
}
}