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))
.nodeRegistry(new JdbcNodeRegistry(jdbcConfig))
.networkHandler(new NioNetworkHandler())
.cryptography(BouncyCryptography.INSTANCE)
.cryptography(new BouncyCryptography())
.listener(System.out::println)
.build();
```

View File

@ -29,6 +29,7 @@ subprojects {
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
}
test {

View File

@ -58,7 +58,53 @@ import kotlin.properties.Delegates
*
* 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
@ -72,38 +118,10 @@ class BitmessageContext private constructor(builder: BitmessageContext.Builder)
val labeler: Labeler
@JvmName("labeler") get
init {
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
val addresses: AddressRepository
@JvmName("addresses") get
val messages: MessageRepository = internals.messageRepository
val messages: MessageRepository
@JvmName("messages") get
fun createIdentity(shorter: Boolean, vararg features: Feature): BitmessageAddress {
@ -285,10 +303,9 @@ class BitmessageContext private constructor(builder: BitmessageContext.Builder)
fun addContact(contact: BitmessageAddress) {
internals.addressRepository.save(contact)
if (contact.pubkey == null) {
internals.addressRepository.getAddress(contact.address)?.let {
if (it.pubkey == null) {
internals.requestPubkey(contact)
}
// If it already existed, the saved contact might have the public key
if (internals.addressRepository.getAddress(contact.address)!!.pubkey == null) {
internals.requestPubkey(contact)
}
}
}
@ -300,13 +317,13 @@ class BitmessageContext private constructor(builder: BitmessageContext.Builder)
}
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 {
val broadcast = `object`.payload as Broadcast
val broadcast = objectMessage.payload as Broadcast
broadcast.decrypt(address)
// 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.
internals.networkListener.receive(`object`)
internals.networkListener.receive(objectMessage)
} catch (ignore: DecryptionFailedException) {
} catch (e: Exception) {
LOG.debug(e.message, e)
@ -348,6 +365,7 @@ class BitmessageContext private constructor(builder: BitmessageContext.Builder)
internal var connectionLimit = 150
internal var connectionTTL = 30 * MINUTE
internal var sendPubkeyOnIdentityCreation = true
internal var doMissingProofOfWorkDelay = 30
fun port(port: Int): Builder {
this.port = port
@ -409,6 +427,7 @@ class BitmessageContext private constructor(builder: BitmessageContext.Builder)
return this
}
@JvmName("kotlinListener")
fun listener(listener: (Plaintext) -> Unit): Builder {
this.listener = object : Listener {
override fun receive(plaintext: Plaintext) {
@ -428,6 +447,10 @@ class BitmessageContext private constructor(builder: BitmessageContext.Builder)
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
* this behaviour might not be desirable.
@ -438,18 +461,34 @@ class BitmessageContext private constructor(builder: BitmessageContext.Builder)
}
fun build(): BitmessageContext {
nonNull("inventory", inventory)
nonNull("nodeRegistry", nodeRegistry)
nonNull("networkHandler", networkHandler)
nonNull("addressRepo", addressRepo)
nonNull("messageRepo", messageRepo)
nonNull("proofOfWorkRepo", proofOfWorkRepository)
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 {

View File

@ -25,6 +25,7 @@ import ch.dissem.bitmessage.entity.valueobject.InventoryVector
import ch.dissem.bitmessage.exception.DecryptionFailedException
import ch.dissem.bitmessage.ports.Labeler
import ch.dissem.bitmessage.ports.NetworkHandler
import ch.dissem.bitmessage.utils.Strings.hex
import org.slf4j.LoggerFactory
import java.util.*
@ -32,23 +33,23 @@ internal open class DefaultMessageListener(
private val labeler: Labeler,
private val listener: BitmessageContext.Listener
) : NetworkHandler.MessageListener {
private var ctx by InternalContext
private var ctx by InternalContext.lateinit
override fun receive(`object`: ObjectMessage) {
val payload = `object`.payload
override fun receive(objectMessage: ObjectMessage) {
val payload = objectMessage.payload
when (payload.type) {
ObjectType.GET_PUBKEY -> {
receive(`object`, payload as GetPubkey)
receive(objectMessage, payload as GetPubkey)
}
ObjectType.PUBKEY -> {
receive(`object`, payload as Pubkey)
receive(objectMessage, payload as Pubkey)
}
ObjectType.MSG -> {
receive(`object`, payload as Msg)
receive(objectMessage, payload as Msg)
}
ObjectType.BROADCAST -> {
receive(`object`, payload as Broadcast)
receive(objectMessage, payload as Broadcast)
}
null -> {
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)
if (identity != null && identity.privateKey != null && !identity.isChan) {
LOG.info("Got pubkey request for identity " + identity)
// 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) {
val address: BitmessageAddress?
protected fun receive(objectMessage: ObjectMessage, pubkey: Pubkey) {
try {
if (pubkey is V4Pubkey) {
address = ctx.addressRepository.findContact(pubkey.tag)
if (address != null) {
pubkey.decrypt(address.publicDecryptionKey)
ctx.addressRepository.findContact(pubkey.tag)?.let {
if (it.pubkey == null) {
pubkey.decrypt(it.publicDecryptionKey)
updatePubkey(it, pubkey)
}
}
} 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()) {
try {
msg.decrypt(identity.privateKey!!.privateEncryptionKey)
val plaintext = msg.plaintext!!
plaintext.to = identity
if (!`object`.isSignatureValid(plaintext.from.pubkey!!)) {
LOG.warn("Msg with IV " + `object`.inventoryVector + " was successfully decrypted, but signature check failed. Ignoring.")
if (!objectMessage.isSignatureValid(plaintext.from.pubkey!!)) {
LOG.warn("Msg with IV " + objectMessage.inventoryVector + " was successfully decrypted, but signature check failed. Ignoring.")
} else {
receive(`object`.inventoryVector, plaintext)
receive(objectMessage.inventoryVector, plaintext)
}
break
} catch (_: DecryptionFailedException) {}
} catch (_: DecryptionFailedException) {
}
}
}
@ -122,11 +127,11 @@ internal open class DefaultMessageListener(
ctx.messageRepository.getMessageForAck(ack.data)?.let {
ctx.labeler.markAsAcknowledged(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
for (subscription in ctx.addressRepository.getSubscriptions(broadcast.version)) {
if (tag != null && !Arrays.equals(tag, subscription.tag)) {
@ -134,12 +139,13 @@ internal open class DefaultMessageListener(
}
try {
broadcast.decrypt(subscription.publicDecryptionKey)
if (!`object`.isSignatureValid(broadcast.plaintext!!.from.pubkey!!)) {
LOG.warn("Broadcast with IV " + `object`.inventoryVector + " was successfully decrypted, but signature check failed. Ignoring.")
if (!objectMessage.isSignatureValid(broadcast.plaintext!!.from.pubkey!!)) {
LOG.warn("Broadcast with IV " + objectMessage.inventoryVector + " was successfully decrypted, but signature check failed. Ignoring.")
} 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 {
ctx.inventory.storeObject(it)
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 java.util.*
import java.util.concurrent.Executors
import kotlin.properties.Delegates
import kotlin.reflect.KProperty
/**
@ -68,7 +67,8 @@ class InternalContext(
get() = _streams.toLongArray()
init {
instance = this
lateinit.instance = this
lateinit = ContextDelegate()
Singleton.initialize(cryptography)
// 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 expires = UnixTime.now + timeToLive
LOG.info("Expires at " + expires)
val `object` = ObjectMessage(
val objectMessage = ObjectMessage(
stream = recipient.stream,
expiresTime = expires,
payload = payload
)
if (`object`.isSigned) {
`object`.sign(
if (objectMessage.isSigned) {
objectMessage.sign(
from.privateKey ?: throw IllegalArgumentException("The given sending address is no identity")
)
}
if (payload is Broadcast) {
payload.encrypt()
} else if (payload is Encrypted) {
`object`.encrypt(
objectMessage.encrypt(
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) {
@ -163,12 +163,11 @@ class InternalContext(
}
val expires = UnixTime.now + TTL.getpubkey
LOG.info("Expires at " + expires)
val payload = GetPubkey(contact)
LOG.info("Expires at $expires")
val request = ObjectMessage(
stream = contact.stream,
expiresTime = expires,
payload = payload
payload = GetPubkey(contact)
)
proofOfWorkService.doProofOfWork(request)
}
@ -179,14 +178,14 @@ class InternalContext(
address.alias = it.alias
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 {
val pubkey = `object`.payload as Pubkey
val pubkey = objectMessage.payload as Pubkey
if (address.version == 4L) {
val v4Pubkey = pubkey as V4Pubkey
if (Arrays.equals(address.tag, v4Pubkey.tag)) {
v4Pubkey.decrypt(address.publicDecryptionKey)
if (`object`.isSignatureValid(v4Pubkey)) {
if (objectMessage.isSignatureValid(v4Pubkey)) {
address.pubkey = v4Pubkey
addressRepository.save(address)
break
@ -219,17 +218,19 @@ class 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 {
private val LOG = LoggerFactory.getLogger(InternalContext::class.java)
@JvmField val NETWORK_NONCE_TRIALS_PER_BYTE: Long = 1000
@JvmField val NETWORK_EXTRA_BYTES: Long = 1000
private var instance: InternalContext by Delegates.notNull<InternalContext>()
operator fun getValue(thisRef: Any?, property: KProperty<*>) = instance
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: InternalContext) {
instance = value
}
var lateinit = ContextDelegate()
private set
}
}

View File

@ -22,6 +22,7 @@ import ch.dissem.bitmessage.entity.*
import ch.dissem.bitmessage.entity.payload.Msg
import ch.dissem.bitmessage.ports.ProofOfWorkEngine
import ch.dissem.bitmessage.ports.ProofOfWorkRepository.Item
import ch.dissem.bitmessage.utils.Strings
import org.slf4j.LoggerFactory
import java.io.IOException
import java.util.*
@ -31,7 +32,7 @@ import java.util.*
*/
class ProofOfWorkService : ProofOfWorkEngine.Callback {
private val ctx by InternalContext
private val ctx by InternalContext.lateinit
private val cryptography by lazy { ctx.cryptography }
private val powRepo by lazy { ctx.proofOfWorkRepository }
private val messageRepo by lazy { ctx.messageRepository }
@ -45,75 +46,69 @@ class ProofOfWorkService : ProofOfWorkEngine.Callback {
override fun run() {
LOG.info("Doing POW for " + items.size + " tasks.")
for (initialHash in items) {
val (`object`, nonceTrialsPerByte, extraBytes) = powRepo.getItem(initialHash)
cryptography.doProofOfWork(`object`, nonceTrialsPerByte, extraBytes,
val (objectMessage, nonceTrialsPerByte, extraBytes) = powRepo.getItem(initialHash)
cryptography.doProofOfWork(objectMessage, nonceTrialsPerByte, extraBytes,
this@ProofOfWorkService)
}
}
}, delayInMilliseconds)
}
fun doProofOfWork(`object`: ObjectMessage) {
doProofOfWork(null, `object`)
fun doProofOfWork(objectMessage: ObjectMessage) {
doProofOfWork(null, objectMessage)
}
fun doProofOfWork(recipient: BitmessageAddress?, `object`: ObjectMessage) {
fun doProofOfWork(recipient: BitmessageAddress?, objectMessage: ObjectMessage) {
val pubkey = recipient?.pubkey
val nonceTrialsPerByte = pubkey?.nonceTrialsPerByte ?: NETWORK_NONCE_TRIALS_PER_BYTE
val extraBytes = pubkey?.extraBytes ?: NETWORK_EXTRA_BYTES
powRepo.putObject(`object`, nonceTrialsPerByte, extraBytes)
if (`object`.payload is PlaintextHolder) {
`object`.payload.plaintext?.let {
it.initialHash = cryptography.getInitialHash(`object`)
powRepo.putObject(objectMessage, nonceTrialsPerByte, extraBytes)
if (objectMessage.payload is PlaintextHolder) {
objectMessage.payload.plaintext?.let {
it.initialHash = cryptography.getInitialHash(objectMessage)
messageRepo.save(it)
}
} ?: LOG.error("PlaintextHolder without Plaintext shouldn't make it to the POW")
}
cryptography.doProofOfWork(`object`, nonceTrialsPerByte, extraBytes, this)
cryptography.doProofOfWork(objectMessage, nonceTrialsPerByte, extraBytes, this)
}
fun doProofOfWorkWithAck(plaintext: Plaintext, expirationTime: Long) {
val ack = plaintext.ackMessage
val ack = plaintext.ackMessage!!
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)
powRepo.putObject(item)
cryptography.doProofOfWork(ack, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES, this)
}
override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) {
val (`object`, _, _, expirationTime, message) = powRepo.getItem(initialHash)
val (objectMessage, _, _, expirationTime, message) = powRepo.getItem(initialHash)
if (message == null) {
`object`.nonce = nonce
objectMessage.nonce = nonce
messageRepo.getMessage(initialHash)?.let {
it.inventoryVector = `object`.inventoryVector
it.inventoryVector = objectMessage.inventoryVector
it.updateNextTry()
ctx.labeler.markAsSent(it)
messageRepo.save(it)
}
try {
ctx.networkListener.receive(`object`)
} catch (e: IOException) {
LOG.debug(e.message, e)
}
ctx.inventory.storeObject(`object`)
ctx.networkHandler.offer(`object`.inventoryVector)
ctx.inventory.storeObject(objectMessage)
ctx.networkHandler.offer(objectMessage.inventoryVector)
} else {
message.ackMessage!!.nonce = nonce
val `object` = ObjectMessage.Builder()
val newObjectMessage = ObjectMessage.Builder()
.stream(message.stream)
.expiresTime(expirationTime!!)
.payload(Msg(message))
.build()
if (`object`.isSigned) {
`object`.sign(message.from.privateKey!!)
if (newObjectMessage.isSigned) {
newObjectMessage.sign(message.from.privateKey!!)
}
if (`object`.payload is Encrypted) {
`object`.encrypt(message.to!!.pubkey!!)
if (newObjectMessage.payload is Encrypted) {
newObjectMessage.encrypt(message.to!!.pubkey!!)
}
doProofOfWork(message.to, `object`)
doProofOfWork(message.to, newObjectMessage)
}
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 fun write(out: OutputStream) {
Encode.varInt(addresses.size.toLong(), out)
Encode.varInt(addresses.size, out)
for (address in addresses) {
address.write(out)
}
}
override fun write(buffer: ByteBuffer) {
Encode.varInt(addresses.size.toLong(), buffer)
Encode.varInt(addresses.size, buffer)
for (address in addresses) {
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 fun write(out: OutputStream) {
Encode.varInt(inventory.size.toLong(), out)
Encode.varInt(inventory.size, out)
for (iv in inventory) {
iv.write(out)
}
}
override fun write(buffer: ByteBuffer) {
Encode.varInt(inventory.size.toLong(), buffer)
Encode.varInt(inventory.size, buffer)
for (iv in inventory) {
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 fun write(out: OutputStream) {
Encode.varInt(inventory.size.toLong(), out)
Encode.varInt(inventory.size, out)
for (iv in inventory) {
iv.write(out)
}
}
override fun write(buffer: ByteBuffer) {
Encode.varInt(inventory.size.toLong(), buffer)
Encode.varInt(inventory.size, buffer)
for (iv in inventory) {
iv.write(buffer)
}

View File

@ -43,7 +43,7 @@ data class NetworkMessage(
@Throws(IOException::class)
override fun write(out: OutputStream) {
// 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)
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
// ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are
// larger than this.
Encode.int32(payloadBytes.size.toLong(), out)
Encode.int32(payloadBytes.size, out)
// checksum
out.write(getChecksum(payloadBytes))
@ -92,7 +92,7 @@ data class NetworkMessage(
private fun writeHeader(out: ByteBuffer): ByteArray {
// 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)
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
// ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are
// larger than this.
Encode.int32(payloadBytes.size.toLong(), out)
Encode.int32(payloadBytes.size, out)
// checksum
out.put(getChecksum(payloadBytes))

View File

@ -17,6 +17,7 @@
package ch.dissem.bitmessage.entity
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.Pubkey.Feature
import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding
@ -38,10 +39,20 @@ import kotlin.collections.HashSet
fun message(encoding: Plaintext.Encoding, subject: String, body: String): ByteArray = when (encoding) {
SIMPLE -> "Subject:$subject\nBody:$body".toByteArray()
EXTENDED -> Message.Builder().subject(subject).body(body).build().zip()
TRIVIAL -> (subject+body).toByteArray()
TRIVIAL -> (subject + body).toByteArray()
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'.
*/
@ -52,7 +63,7 @@ class Plaintext private constructor(
val encodingCode: Long,
val message: ByteArray,
val ackData: ByteArray?,
ackMessage: Lazy<ObjectMessage?>,
ackMessage: Lazy<ObjectMessage?> = lazy { Factory.createAck(from, ackData, ttl) },
val conversationId: UUID = UUID.randomUUID(),
var inventoryVector: InventoryVector? = null,
var signature: ByteArray? = null,
@ -121,7 +132,7 @@ class Plaintext private constructor(
to: BitmessageAddress?,
encoding: Encoding,
message: ByteArray,
ackData: ByteArray = cryptography().randomBytes(Msg.ACK_LENGTH),
ackData: ByteArray? = null,
conversationId: UUID = UUID.randomUUID(),
inventoryVector: InventoryVector? = null,
signature: ByteArray? = null,
@ -131,21 +142,20 @@ class Plaintext private constructor(
labels: MutableSet<Label> = HashSet(),
status: Status
) : this(
type,
from,
to,
encoding.code,
message,
ackData,
lazy { Factory.createAck(from, ackData, ttl) },
conversationId,
inventoryVector,
signature,
received,
initialHash,
ttl,
labels,
status
type = type,
from = from,
to = to,
encoding = encoding.code,
message = message,
ackMessage = ackData(type, ackData),
conversationId = conversationId,
inventoryVector = inventoryVector,
signature = signature,
received = received,
initialHash = initialHash,
ttl = ttl,
labels = labels,
status = status
)
constructor(
@ -164,13 +174,13 @@ class Plaintext private constructor(
labels: MutableSet<Label> = HashSet(),
status: Status
) : this(
type,
from,
to,
encoding,
message,
null,
lazy {
type = type,
from = from,
to = to,
encodingCode = encoding,
message = message,
ackData = null,
ackMessage = lazy {
if (ackMessage != null && ackMessage.isNotEmpty()) {
Factory.getObjectMessage(
3,
@ -178,14 +188,14 @@ class Plaintext private constructor(
ackMessage.size)
} else null
},
conversationId,
inventoryVector,
signature,
received,
initialHash,
ttl,
labels,
status
conversationId = conversationId,
inventoryVector = inventoryVector,
signature = signature,
received = received,
initialHash = initialHash,
ttl = ttl,
labels = labels,
status = status
)
constructor(
@ -195,37 +205,36 @@ class Plaintext private constructor(
encoding: Encoding = SIMPLE,
subject: String,
body: String,
ackData: ByteArray = cryptography().randomBytes(Msg.ACK_LENGTH),
ackData: ByteArray? = null,
conversationId: UUID = UUID.randomUUID(),
ttl: Long = TTL.msg,
labels: MutableSet<Label> = HashSet(),
status: Status = Status.DRAFT
) : this(
type,
from,
to,
encoding.code,
message(encoding, subject, body),
ackData,
lazy { Factory.createAck(from, ackData, ttl) },
conversationId,
null,
null,
null,
null,
ttl,
labels,
status
type = type,
from = from,
to = to,
encoding = encoding.code,
message = message(encoding, subject, body),
ackMessage = ackData(type, ackData),
conversationId = conversationId,
inventoryVector = null,
signature = null,
received = null,
initialHash = null,
ttl = ttl,
labels = labels,
status = status
)
constructor(builder: Builder) : this(
builder.type,
builder.from ?: throw IllegalStateException("sender identity not set"),
builder.to,
builder.encoding,
builder.message,
builder.ackData,
lazy {
type = builder.type,
from = builder.from ?: throw IllegalStateException("sender identity not set"),
to = builder.to,
encodingCode = builder.encoding,
message = builder.message,
ackData = builder.ackData,
ackMessage = lazy {
val ackMsg = builder.ackMessage
if (ackMsg != null && ackMsg.isNotEmpty()) {
Factory.getObjectMessage(
@ -236,15 +245,17 @@ class Plaintext private constructor(
Factory.createAck(builder.from!!, builder.ackData, builder.ttl)
}
},
builder.conversation ?: UUID.randomUUID(),
builder.inventoryVector,
builder.signature,
builder.received,
null,
builder.ttl,
builder.labels,
builder.status ?: Status.RECEIVED
)
conversationId = builder.conversation ?: UUID.randomUUID(),
inventoryVector = builder.inventoryVector,
signature = builder.signature,
received = builder.received,
initialHash = null,
ttl = builder.ttl,
labels = builder.labels,
status = builder.status ?: Status.RECEIVED
) {
id = builder.id
}
fun write(out: OutputStream, includeSignature: Boolean) {
Encode.varInt(from.version, out)
@ -259,7 +270,7 @@ class Plaintext private constructor(
Encode.varInt(0, out)
}
} 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!!.encryptionKey, 1, 64)
if (from.version >= 3) {
@ -267,13 +278,13 @@ class Plaintext private constructor(
Encode.varInt(from.pubkey!!.extraBytes, out)
}
}
if (type == Type.MSG) {
if (type == MSG) {
out.write(to!!.ripe)
}
Encode.varInt(encodingCode, out)
Encode.varInt(message.size.toLong(), out)
Encode.varInt(message.size, out)
out.write(message)
if (type == Type.MSG) {
if (type == MSG) {
if (to?.has(Feature.DOES_ACK) ?: false) {
val ack = ByteArrayOutputStream()
ackMessage?.write(ack)
@ -286,8 +297,7 @@ class Plaintext private constructor(
if (signature == null) {
Encode.varInt(0, out)
} else {
Encode.varInt(signature!!.size.toLong(), out)
out.write(signature!!)
Encode.varBytes(signature!!, out)
}
}
}
@ -305,7 +315,7 @@ class Plaintext private constructor(
Encode.varInt(0, buffer)
}
} 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!!.encryptionKey, 1, 64)
if (from.version >= 3) {
@ -313,13 +323,12 @@ class Plaintext private constructor(
Encode.varInt(from.pubkey!!.extraBytes, buffer)
}
}
if (type == Type.MSG) {
if (type == MSG) {
buffer.put(to!!.ripe)
}
Encode.varInt(encodingCode, buffer)
Encode.varInt(message.size.toLong(), buffer)
buffer.put(message)
if (type == Type.MSG) {
Encode.varBytes(message, buffer)
if (type == MSG) {
if (to!!.has(Feature.DOES_ACK) && ackMessage != null) {
Encode.varBytes(Encode.bytes(ackMessage!!), buffer)
} else {
@ -331,8 +340,7 @@ class Plaintext private constructor(
if (sig == null) {
Encode.varInt(0, buffer)
} else {
Encode.varInt(sig.size.toLong(), buffer)
buffer.put(sig)
Encode.varBytes(sig, buffer)
}
}
}
@ -365,7 +373,7 @@ class Plaintext private constructor(
val firstLine = s.nextLine()
if (encodingCode == EXTENDED.code) {
if (Message.TYPE == extendedData?.type) {
return (extendedData!!.content as Message?)?.subject
return (extendedData!!.content as? Message)?.subject
} else {
return null
}
@ -551,10 +559,12 @@ class Plaintext private constructor(
return this
}
fun to(address: BitmessageAddress): Builder {
if (type != Type.MSG && to != null)
throw IllegalArgumentException("recipient address only allowed for msg")
to = address
fun to(address: BitmessageAddress?): Builder {
if (address != null) {
if (type != MSG && to != null)
throw IllegalArgumentException("recipient address only allowed for msg")
to = address
}
return this
}
@ -594,7 +604,7 @@ class Plaintext private constructor(
}
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
return this
}
@ -622,7 +632,6 @@ class Plaintext private constructor(
} catch (e: UnsupportedEncodingException) {
throw ApplicationException(e)
}
return this
}
@ -632,13 +641,13 @@ class Plaintext private constructor(
}
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
return this
}
fun ackData(ackData: ByteArray?): Builder {
if (type != Type.MSG && ackData != null)
if (type != MSG && ackData != null)
throw IllegalArgumentException("ackMessage only allowed for msg")
this.ackData = ackData
return this
@ -704,7 +713,7 @@ class Plaintext private constructor(
if (to == null && type != Type.BROADCAST && destinationRipe != null) {
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)
}
if (ttl <= 0) {
@ -733,10 +742,10 @@ class Plaintext private constructor(
.publicEncryptionKey(Decode.bytes(`in`, 64))
.nonceTrialsPerByte(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`))
.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 fun write(out: OutputStream) {
Encode.int32(version.toLong(), out)
Encode.int32(version, out)
Encode.int64(services, out)
Encode.int64(timestamp, out)
addrRecv.write(out, true)
@ -89,7 +89,7 @@ class Version constructor(
}
override fun write(buffer: ByteBuffer) {
Encode.int32(version.toLong(), buffer)
Encode.int32(version, buffer)
Encode.int64(services, buffer)
Encode.int64(timestamp, buffer)
addrRecv.write(buffer, true)

View File

@ -114,7 +114,7 @@ class CryptoBox : Streamable {
private fun writeWithoutMAC(out: OutputStream) {
out.write(initializationVector)
Encode.int16(curveType.toLong(), out)
Encode.int16(curveType, out)
writeCoordinateComponent(out, Points.getX(R))
writeCoordinateComponent(out, Points.getY(R))
out.write(encrypted)
@ -123,14 +123,14 @@ class CryptoBox : Streamable {
private fun writeCoordinateComponent(out: OutputStream, x: ByteArray) {
val offset = Bytes.numberOfLeadingZeros(x)
val length = x.size - offset
Encode.int16(length.toLong(), out)
Encode.int16(length, out)
out.write(x, offset, length)
}
private fun writeCoordinateComponent(buffer: ByteBuffer, x: ByteArray) {
val offset = Bytes.numberOfLeadingZeros(x)
val length = x.size - offset
Encode.int16(length.toLong(), buffer)
Encode.int16(length, buffer)
buffer.put(x, offset, length)
}
@ -141,7 +141,7 @@ class CryptoBox : Streamable {
override fun write(buffer: ByteBuffer) {
buffer.put(initializationVector)
Encode.int16(curveType.toLong(), buffer)
Encode.int16(curveType, buffer)
writeCoordinateComponent(buffer, Points.getX(R))
writeCoordinateComponent(buffer, Points.getY(R))
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 fun write(out: OutputStream) {
Encode.int32(behaviorBitfield.toLong(), out)
Encode.int32(behaviorBitfield, out)
out.write(signingKey, 1, 64)
out.write(encryptionKey, 1, 64)
}
override fun write(buffer: ByteBuffer) {
Encode.int32(behaviorBitfield.toLong(), buffer)
Encode.int32(behaviorBitfield, buffer)
buffer.put(signingKey, 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.util.*
data class Label(private val label: String, val type: Label.Type,
/**
* RGBA representation for the color.
*/
var color: Int) : Serializable {
data class Label(
private val label: String,
val type: Label.Type?,
/**
* RGBA representation for the color.
*/
var color: Int
) : Serializable {
var id: Any? = null

View File

@ -30,7 +30,7 @@ import java.util.*
/**
* A node's address. It's written in IPv6 format.
*/
data class NetworkAddress constructor(
data class NetworkAddress(
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
* (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
) : Streamable {
fun provides(service: Version.Service?): Boolean = service?.isEnabled(services) ?: false
fun toInetAddress(): InetAddress {
return InetAddress.getByAddress(iPv6)
return InetAddress.getByAddress(IPv6)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
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 {
var result = Arrays.hashCode(iPv6)
var result = Arrays.hashCode(IPv6)
result = 31 * result + port
return result
}
@ -84,8 +84,8 @@ data class NetworkAddress constructor(
Encode.int32(stream, out)
}
Encode.int64(services, out)
out.write(iPv6)
Encode.int16(port.toLong(), out)
out.write(IPv6)
Encode.int16(port, out)
}
override fun write(buffer: ByteBuffer) {
@ -98,8 +98,8 @@ data class NetworkAddress constructor(
Encode.int32(stream, buffer)
}
Encode.int64(services, buffer)
buffer.put(iPv6)
Encode.int16(port.toLong(), buffer)
buffer.put(IPv6)
Encode.int16(port, buffer)
}
class Builder {

View File

@ -121,12 +121,10 @@ class PrivateKey : Streamable {
Encode.varInt(pubkey.stream, out)
val baos = ByteArrayOutputStream()
pubkey.writeUnencrypted(baos)
Encode.varInt(baos.size().toLong(), out)
Encode.varInt(baos.size(), out)
out.write(baos.toByteArray())
Encode.varInt(privateSigningKey.size.toLong(), out)
out.write(privateSigningKey)
Encode.varInt(privateEncryptionKey.size.toLong(), out)
out.write(privateEncryptionKey)
Encode.varBytes(privateSigningKey, out)
Encode.varBytes(privateEncryptionKey, out)
}

View File

@ -37,7 +37,7 @@ import javax.crypto.spec.SecretKeySpec
* Implements everything that isn't directly dependent on either Spongy- or Bouncycastle.
*/
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_SHA1 = "SHA1withECDSA"
@ -87,21 +87,21 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va
return result
}
override fun doProofOfWork(`object`: ObjectMessage, nonceTrialsPerByte: Long,
override fun doProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long,
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))
context.proofOfWorkEngine.calculateNonce(initialHash, target, callback)
}
@Throws(InsufficientProofOfWorkException::class)
override fun checkProofOfWork(`object`: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) {
val target = getProofOfWorkTarget(`object`, nonceTrialsPerByte, extraBytes)
val value = doubleSha512(`object`.nonce ?: throw ApplicationException("Object without nonce"), getInitialHash(`object`))
override fun checkProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) {
val target = getProofOfWorkTarget(objectMessage, nonceTrialsPerByte, extraBytes)
val value = doubleSha512(objectMessage.nonce ?: throw ApplicationException("Object without nonce"), getInitialHash(objectMessage))
if (Bytes.lt(target, value, 8)) {
throw InsufficientProofOfWorkException(target, value)
}
@ -130,18 +130,18 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va
return false
}
override fun getInitialHash(`object`: ObjectMessage): ByteArray {
return sha512(`object`.payloadBytesWithoutNonce)
override fun getInitialHash(objectMessage: ObjectMessage): ByteArray {
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")
val nonceTrialsPerByte = if (nonceTrialsPerByte == 0L) NETWORK_NONCE_TRIALS_PER_BYTE else nonceTrialsPerByte
@Suppress("NAME_SHADOWING")
val extraBytes = if (extraBytes == 0L) NETWORK_EXTRA_BYTES else extraBytes
val TTL = BigInteger.valueOf(`object`.expiresTime - UnixTime.now)
val powLength = BigInteger.valueOf(`object`.payloadBytesWithoutNonce.size + extraBytes)
val TTL = BigInteger.valueOf(objectMessage.expiresTime - UnixTime.now)
val powLength = BigInteger.valueOf(objectMessage.payloadBytesWithoutNonce.size + extraBytes)
val denominator = BigInteger.valueOf(nonceTrialsPerByte)
.multiply(
powLength.add(

View File

@ -28,19 +28,19 @@ import ch.dissem.bitmessage.utils.UnixTime
import java.util.*
abstract class AbstractMessageRepository : MessageRepository {
protected var ctx by InternalContext
protected var ctx by InternalContext.lateinit
protected fun saveContactIfNecessary(contact: BitmessageAddress?) {
contact?.let {
val savedAddress = ctx.addressRepository.getAddress(contact.address)
val savedAddress = ctx.addressRepository.getAddress(it.address)
if (savedAddress == null) {
ctx.addressRepository.save(contact)
} else if (savedAddress.pubkey == null && contact.pubkey != null) {
savedAddress.pubkey = contact.pubkey
ctx.addressRepository.save(savedAddress)
}
if (savedAddress != null) {
contact.alias = savedAddress.alias
ctx.addressRepository.save(it)
} else {
if (savedAddress.pubkey == null && it.pubkey != null) {
savedAddress.pubkey = it.pubkey
ctx.addressRepository.save(savedAddress)
}
it.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
* live.
* @param object to do the proof of work for
* @param objectMessage to do the proof of work for
* *
* @param nonceTrialsPerByte difficulty
* *
@ -153,11 +153,11 @@ interface Cryptography {
* *
* @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)
/**
* @param object to be checked
* @param objectMessage to be checked
* *
* @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::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)

View File

@ -23,7 +23,7 @@ import ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST
import ch.dissem.bitmessage.entity.valueobject.Label
open class DefaultLabeler : Labeler {
private var ctx by InternalContext
private var ctx by InternalContext.lateinit
override fun setLabels(msg: Plaintext) {
msg.status = RECEIVED

View File

@ -43,9 +43,9 @@ interface Inventory {
*/
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

View File

@ -28,7 +28,7 @@ interface MessageRepository {
fun getLabels(vararg types: Label.Type): List<Label>
fun countUnread(label: Label): Int
fun countUnread(label: Label?): Int
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.
*/
fun findConversations(label: Label): List<UUID>
fun findConversations(label: Label?): List<UUID>
fun findMessages(label: Label?): List<Plaintext>

View File

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

View File

@ -29,14 +29,14 @@ interface ProofOfWorkRepository {
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 removeObject(initialHash: ByteArray)
data class Item @JvmOverloads constructor(
val `object`: ObjectMessage,
val objectMessage: ObjectMessage,
val nonceTrialsPerByte: Long,
val extraBytes: Long,
// 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.valueobject.InventoryVector
import ch.dissem.bitmessage.ports.MessageRepository
import org.slf4j.LoggerFactory
import java.util.*
import java.util.Collections
import java.util.regex.Pattern

View File

@ -27,19 +27,20 @@ import java.nio.ByteBuffer
*/
object Encode {
@JvmStatic fun varIntList(values: LongArray, stream: OutputStream) {
varInt(values.size.toLong(), stream)
varInt(values.size, stream)
for (value in values) {
varInt(value, stream)
}
}
@JvmStatic fun varIntList(values: LongArray, buffer: ByteBuffer) {
varInt(values.size.toLong(), buffer)
varInt(values.size, buffer)
for (value in values) {
varInt(value, buffer)
}
}
@JvmStatic fun varInt(value: Int, buffer: ByteBuffer) = varInt(value.toLong(), buffer)
@JvmStatic fun varInt(value: Long, buffer: ByteBuffer) {
if (value < 0) {
// 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 {
val buffer = ByteBuffer.allocate(9)
varInt(value, buffer)
@ -69,6 +71,7 @@ object Encode {
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) {
val buffer = ByteBuffer.allocate(9)
varInt(value, buffer)
@ -77,27 +80,34 @@ object Encode {
AccessCounter.inc(counter, buffer.limit())
}
@JvmStatic @JvmOverloads fun int8(value: Long, stream: OutputStream, counter: AccessCounter? = null) {
stream.write(value.toInt())
@JvmStatic @JvmOverloads fun int8(value: Long, stream: OutputStream, counter: AccessCounter? = null) = int8(value.toInt(), stream, counter)
@JvmStatic @JvmOverloads fun int8(value: Int, stream: OutputStream, counter: AccessCounter? = null) {
stream.write(value)
AccessCounter.inc(counter)
}
@JvmStatic @JvmOverloads fun int16(value: Long, stream: OutputStream, counter: AccessCounter? = null) {
stream.write(ByteBuffer.allocate(2).putShort(value.toShort()).array())
@JvmStatic @JvmOverloads fun int16(value: Long, stream: OutputStream, counter: AccessCounter? = null) = int16(value.toShort(), stream, counter)
@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)
}
@JvmStatic fun int16(value: Long, buffer: ByteBuffer) {
buffer.putShort(value.toShort())
@JvmStatic fun int16(value: Long, buffer: ByteBuffer) = int16(value.toShort(), buffer)
@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) {
stream.write(ByteBuffer.allocate(4).putInt(value.toInt()).array())
@JvmStatic @JvmOverloads fun int32(value: Long, stream: OutputStream, counter: AccessCounter? = null) = int32(value.toInt(), stream, counter)
@JvmStatic @JvmOverloads fun int32(value: Int, stream: OutputStream, counter: AccessCounter? = null) {
stream.write(ByteBuffer.allocate(4).putInt(value).array())
AccessCounter.inc(counter, 4)
}
@JvmStatic fun int32(value: Long, buffer: ByteBuffer) {
buffer.putInt(value.toInt())
@JvmStatic fun int32(value: Long, buffer: ByteBuffer) = int32(value.toInt(), buffer)
@JvmStatic fun int32(value: Int, buffer: ByteBuffer) {
buffer.putInt(value)
}
@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.ProofOfWorkRepository
import ch.dissem.bitmessage.testutils.TestInventory
import ch.dissem.bitmessage.utils.Singleton
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.TestUtils
import ch.dissem.bitmessage.utils.UnixTime.MINUTE
@ -40,12 +40,12 @@ import org.junit.Assert.*
import org.junit.Before
import org.junit.Test
import java.util.*
import kotlin.concurrent.thread
/**
* @author Christian Basler
*/
class BitmessageContextTest {
private lateinit var ctx: BitmessageContext
private var listener: BitmessageContext.Listener = mock()
private val inventory = spy(TestInventory())
private val testPowRepo = spy(object : ProofOfWorkRepository {
@ -54,7 +54,7 @@ class BitmessageContextTest {
internal var removed = 0
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> {
@ -66,12 +66,12 @@ class BitmessageContextTest {
}
override fun putObject(item: ProofOfWorkRepository.Item) {
items.put(InventoryVector(cryptography().getInitialHash(item.`object`)), item)
items.put(InventoryVector(cryptography().getInitialHash(item.objectMessage)), item)
added++
}
override fun putObject(`object`: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) {
items.put(InventoryVector(cryptography().getInitialHash(`object`)), ProofOfWorkRepository.Item(`object`, nonceTrialsPerByte, extraBytes))
override fun putObject(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) {
items.put(InventoryVector(cryptography().getInitialHash(objectMessage)), ProofOfWorkRepository.Item(objectMessage, nonceTrialsPerByte, extraBytes))
added++
}
@ -87,39 +87,42 @@ class BitmessageContextTest {
removed = 0
}
})
private val testPowEngine = spy(object : ProofOfWorkEngine {
override fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: ProofOfWorkEngine.Callback) {
thread { callback.onNonceCalculated(initialHash, ByteArray(8)) }
}
})
private var ctx = BitmessageContext.Builder()
.addressRepo(mock())
.cryptography(BouncyCryptography())
.inventory(inventory)
.listener(listener)
.messageRepo(mock())
.networkHandler(mock())
.nodeRegistry(mock())
.labeler(spy(DefaultLabeler()))
.powRepo(testPowRepo)
.proofOfWorkEngine(testPowEngine)
.build()
init {
TTL.msg = 2 * MINUTE
}
@Before
fun setUp() {
Singleton.initialize(BouncyCryptography())
ctx = BitmessageContext.Builder()
.addressRepo(mock())
.cryptography(cryptography())
.inventory(inventory)
.listener(listener)
.messageRepo(mock())
.networkHandler(mock())
.nodeRegistry(mock())
.labeler(spy(DefaultLabeler()))
.powRepo(testPowRepo)
.proofOfWorkEngine(spy(object : ProofOfWorkEngine {
override fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: ProofOfWorkEngine.Callback) {
callback.onNonceCalculated(initialHash, ByteArray(8))
}
}))
.build()
TTL.msg = 2 * MINUTE
testPowRepo.reset()
}
@Test
fun `ensure contact is saved and pubkey requested`() {
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)
verify(ctx.addresses, timeout(1000).atLeastOnce()).save(contact)
verify(ctx.internals.proofOfWorkEngine, timeout(1000)).calculateNonce(any(), any(), any())
verify(ctx.addresses, timeout(1000).atLeastOnce()).save(eq(contact))
verify(testPowEngine, timeout(1000)).calculateNonce(any(), any(), any())
}
@Test
@ -132,7 +135,7 @@ class BitmessageContextTest {
ctx.addContact(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
@ -155,7 +158,7 @@ class BitmessageContextTest {
ctx.addContact(contact)
verify(ctx.addresses, atLeastOnce()).save(contact)
verify(ctx.internals.proofOfWorkEngine, never()).calculateNonce(any(), any(), any())
verify(testPowEngine, never()).calculateNonce(any(), any(), any())
}
@Test
@ -179,7 +182,7 @@ class BitmessageContextTest {
ctx.addContact(contact)
verify(ctx.addresses, atLeastOnce()).save(any())
verify(ctx.internals.proofOfWorkEngine, never()).calculateNonce(any(), any(), any())
verify(testPowEngine, never()).calculateNonce(any(), any(), any())
}
@Test
@ -209,9 +212,9 @@ class BitmessageContextTest {
fun `ensure message is sent`() {
ctx.send(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), TestUtils.loadContact(),
"Subject", "Message")
verify(ctx.internals.proofOfWorkRepository, timeout(10000)).putObject(
argThat { payload.type == ObjectType.MSG }, eq(1000L), eq(1000L))
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 })
}
@ -236,10 +239,9 @@ class BitmessageContextTest {
fun `ensure broadcast is sent`() {
ctx.broadcast(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"),
"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))
verify(ctx.internals.proofOfWorkEngine)
.calculateNonce(any(), any(), any())
verify(testPowEngine).calculateNonce(any(), any(), any())
verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat { type == Type.BROADCAST })
}
@ -265,7 +267,7 @@ class BitmessageContextTest {
fun `ensure deterministic addresses are created`() {
val expected_size = 8
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)
expected.add("BM-2cWFkyuXXFw6d393RGnin2RpSXj8wxtt6F")
expected.add("BM-2cX8TF9vuQZEWvT7UrEeq1HN9dgiSUPLEN")
@ -285,7 +287,7 @@ class BitmessageContextTest {
fun `ensure short deterministic addresses are created`() {
val expected_size = 1
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)
expected.add("BM-NBGyBAEp6VnBkFWKpzUSgxuTqVdWPi78")
for (a in addresses) {

View File

@ -16,6 +16,7 @@
package ch.dissem.bitmessage
import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography
import ch.dissem.bitmessage.entity.BitmessageAddress
import ch.dissem.bitmessage.entity.ObjectMessage
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.ObjectType
import ch.dissem.bitmessage.factory.Factory
import ch.dissem.bitmessage.ports.ProofOfWorkRepository
import ch.dissem.bitmessage.utils.Singleton
import ch.dissem.bitmessage.utils.TestBase
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 org.junit.Before
import org.junit.Test
@ -40,7 +44,7 @@ class DefaultMessageListenerTest : TestBase() {
private lateinit var listener: DefaultMessageListener
private val ctx = TestUtils.mockedInternalContext(
cryptography = Singleton.cryptography()
cryptography = BouncyCryptography()
)
@Before
@ -52,10 +56,13 @@ class DefaultMessageListenerTest : TestBase() {
fun `ensure pubkey is sent on request`() {
val identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")
whenever(ctx.addressRepository.findIdentity(any())).thenReturn(identity)
listener.receive(ObjectMessage.Builder()
.stream(2)
.payload(GetPubkey(BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")))
.build())
val objectMessage = ObjectMessage(
stream = 2,
payload = GetPubkey(BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")),
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())
}
@ -73,6 +80,7 @@ class DefaultMessageListenerTest : TestBase() {
.build()
objectMessage.sign(identity.privateKey!!)
objectMessage.encrypt(Singleton.cryptography().createPublicKey(identity.publicDecryptionKey))
whenever(ctx.proofOfWorkRepository.getItem(any())).thenReturn(ProofOfWorkRepository.Item(objectMessage, 1000L, 1000L))
listener.receive(objectMessage)
verify(ctx.addressRepository).save(eq(contact))

View File

@ -77,24 +77,24 @@ class ProofOfWorkServiceTest {
val identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")
val address = TestUtils.loadContact()
val plaintext = Plaintext.Builder(MSG).from(identity).to(address).message("", "").build()
val `object` = ObjectMessage(
val objectMessage = ObjectMessage(
expiresTime = 0,
stream = 1,
payload = Msg(plaintext)
)
`object`.sign(identity.privateKey!!)
`object`.encrypt(address.pubkey!!)
objectMessage.sign(identity.privateKey!!)
objectMessage.encrypt(address.pubkey!!)
val initialHash = ByteArray(64)
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)
ctx.proofOfWorkService.onNonceCalculated(initialHash, nonce)
verify(ctx.proofOfWorkRepository).removeObject(eq(initialHash))
verify(ctx.inventory).storeObject(eq(`object`))
verify(ctx.networkHandler).offer(eq(`object`.inventoryVector))
assertThat(plaintext.inventoryVector, equalTo(`object`.inventoryVector))
verify(ctx.inventory).storeObject(eq(objectMessage))
verify(ctx.networkHandler).offer(eq(objectMessage.inventoryVector))
assertThat(plaintext.inventoryVector, equalTo(objectMessage.inventoryVector))
}
}

View File

@ -29,9 +29,9 @@ import org.junit.Test
class SignatureTest : TestBase() {
@Test
fun `ensure validation works`() {
val `object` = TestUtils.loadObjectMessage(3, "V3Pubkey.payload")
val pubkey = `object`.payload as Pubkey
assertTrue(`object`.isSignatureValid(pubkey))
val objectMessage = TestUtils.loadObjectMessage(3, "V3Pubkey.payload")
val pubkey = objectMessage.payload as Pubkey
assertTrue(objectMessage.isSignatureValid(pubkey))
}
@Test
@ -52,11 +52,11 @@ class SignatureTest : TestBase() {
fun `ensure message is properly signed`() {
val identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")
val `object` = TestUtils.loadObjectMessage(3, "V1Msg.payload")
val msg = `object`.payload as Msg
val objectMessage = TestUtils.loadObjectMessage(3, "V1Msg.payload")
val msg = objectMessage.payload as Msg
msg.decrypt(identity.privateKey!!.privateEncryptionKey)
assertNotNull(msg.plaintext)
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() {
@Test
fun `ensure feature flag is calculated correctly`() {
Assert.assertEquals(1, Pubkey.Feature.bitfield(DOES_ACK).toLong())
assertEquals(2, Pubkey.Feature.bitfield(INCLUDE_DESTINATION).toLong())
assertEquals(3, Pubkey.Feature.bitfield(DOES_ACK, INCLUDE_DESTINATION).toLong())
Assert.assertEquals(1, Pubkey.Feature.bitfield(DOES_ACK))
assertEquals(2, Pubkey.Feature.bitfield(INCLUDE_DESTINATION))
assertEquals(3, Pubkey.Feature.bitfield(DOES_ACK, INCLUDE_DESTINATION))
}
@Test
@ -84,9 +84,9 @@ class BitmessageAddressTest : TestBase() {
val address = BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ")
Assert.assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), address.ripe)
val `object` = TestUtils.loadObjectMessage(3, "V3Pubkey.payload")
val pubkey = `object`.payload as Pubkey
assertTrue(`object`.isSignatureValid(pubkey))
val objectMessage = TestUtils.loadObjectMessage(3, "V3Pubkey.payload")
val pubkey = objectMessage.payload as Pubkey
assertTrue(objectMessage.isSignatureValid(pubkey))
try {
address.pubkey = pubkey
} catch (e: Exception) {
@ -100,10 +100,10 @@ class BitmessageAddressTest : TestBase() {
@Test
fun `ensure V4Pubkey can be imported`() {
val address = BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h")
val `object` = TestUtils.loadObjectMessage(4, "V4Pubkey.payload")
`object`.decrypt(address.publicDecryptionKey)
val pubkey = `object`.payload as V4Pubkey
assertTrue(`object`.isSignatureValid(pubkey))
val objectMessage = TestUtils.loadObjectMessage(4, "V4Pubkey.payload")
objectMessage.decrypt(address.publicDecryptionKey)
val pubkey = objectMessage.payload as V4Pubkey
assertTrue(objectMessage.isSignatureValid(pubkey))
try {
address.pubkey = pubkey
} catch (e: Exception) {

View File

@ -170,12 +170,12 @@ class SerializationTest : TestBase() {
private fun doTest(resourceName: String, version: Int, expectedPayloadType: Class<*>) {
val data = TestUtils.getBytes(resourceName)
val `in` = ByteArrayInputStream(data)
val `object` = Factory.getObjectMessage(version, `in`, data.size)
val objectMessage = Factory.getObjectMessage(version, `in`, data.size)
val out = ByteArrayOutputStream()
assertNotNull(`object`)
`object`!!.write(out)
assertNotNull(objectMessage)
objectMessage!!.write(out)
assertArrayEquals(data, out.toByteArray())
assertEquals(expectedPayloadType.canonicalName, `object`.payload.javaClass.canonicalName)
assertEquals(expectedPayloadType.canonicalName, objectMessage.payload.javaClass.canonicalName)
}
@Test

View File

@ -42,12 +42,12 @@ class TestInventory : Inventory {
return ArrayList(inventory.values)
}
override fun storeObject(`object`: ObjectMessage) {
inventory.put(`object`.inventoryVector, `object`)
override fun storeObject(objectMessage: ObjectMessage) {
inventory.put(objectMessage.inventoryVector, objectMessage)
}
override fun contains(`object`: ObjectMessage): Boolean {
return inventory.containsKey(`object`.inventoryVector)
override fun contains(objectMessage: ObjectMessage): Boolean {
return inventory.containsKey(objectMessage.inventoryVector)
}
override fun cleanup() {

View File

@ -27,6 +27,6 @@ class CollectionsTest {
fun `ensure select random returns maximum possible items`() {
val list = LinkedList<Int>()
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
import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography
import ch.dissem.bitmessage.entity.BitmessageAddress
import ch.dissem.bitmessage.entity.Plaintext
import ch.dissem.bitmessage.entity.Plaintext.Type.MSG
@ -30,19 +29,13 @@ import org.junit.Assert.assertThat
import org.junit.Test
import java.util.*
class ConversationServiceTest {
class ConversationServiceTest : TestBase() {
private val alice = BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")
private val bob = BitmessageAddress("BM-2cTtkBnb4BUYDndTKun6D9PjtueP2h1bQj")
private val messageRepository = mock<MessageRepository>()
private val conversationService = spy(ConversationService(messageRepository))
companion object {
init {
Singleton.initialize(BouncyCryptography())
}
}
@Test
fun `ensure conversation is sorted properly`() {
MockitoKotlin.registerInstanceCreator { UUID.randomUUID() }

View File

@ -111,11 +111,11 @@ class EncodeTest {
fun checkBytes(stream: ByteArrayOutputStream, vararg bytes: Int) {
assertEquals(bytes.size.toLong(), stream.size().toLong())
assertEquals(bytes.size, stream.size())
val streamBytes = stream.toByteArray()
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 {
val out = ByteArrayOutputStream()
Encode.int16(number.toLong(), out)
Encode.int16(number, out)
return out.toByteArray()
}
@ -85,9 +85,9 @@ object TestUtils {
@Throws(DecryptionFailedException::class)
@JvmStatic fun loadContact(): BitmessageAddress {
val address = BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h")
val `object` = TestUtils.loadObjectMessage(3, "V4Pubkey.payload")
`object`.decrypt(address.publicDecryptionKey)
address.pubkey = `object`.payload as V4Pubkey
val objectMessage = TestUtils.loadObjectMessage(3, "V4Pubkey.payload")
objectMessage.decrypt(address.publicDecryptionKey)
address.pubkey = objectMessage.payload as V4Pubkey
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),
* this is the Bouncycastle implementation.
*/
object BouncyCryptography : AbstractCryptography(BouncyCastleProvider()) {
private val EC_CURVE_PARAMETERS = CustomNamedCurves.getByName("secp256k1")
class BouncyCryptography : AbstractCryptography(BouncyCastleProvider()) {
override fun crypt(encrypt: Boolean, data: ByteArray, key_e: ByteArray, initializationVector: ByteArray): ByteArray {
val cipher = PaddedBufferedBlockCipher(
@ -119,4 +118,8 @@ object BouncyCryptography : AbstractCryptography(BouncyCastleProvider()) {
BigInteger(1, y)
).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(""
+ "cd566972b5e50104011a92b59fa8e0b1234851ae")
private val crypto = BouncyCryptography
private val crypto = BouncyCryptography()
init {
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),
* this is the Spongycastle implementation.
*/
object SpongyCryptography : AbstractCryptography(BouncyCastleProvider()) {
private val EC_CURVE_PARAMETERS = CustomNamedCurves.getByName("secp256k1")
class SpongyCryptography : AbstractCryptography(BouncyCastleProvider()) {
override fun crypt(encrypt: Boolean, data: ByteArray, key_e: ByteArray, initializationVector: ByteArray): ByteArray {
val cipher = PaddedBufferedBlockCipher(
@ -119,4 +118,8 @@ object SpongyCryptography : AbstractCryptography(BouncyCastleProvider()) {
BigInteger(1, y)
).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(""
+ "cd566972b5e50104011a92b59fa8e0b1234851ae")
private val crypto = SpongyCryptography
private val crypto = SpongyCryptography()
init {
Singleton.initialize(crypto)

View File

@ -61,7 +61,7 @@ public class Main {
.messageRepo(new JdbcMessageRepository(jdbcConfig))
.powRepo(new JdbcProofOfWorkRepository(jdbcConfig))
.networkHandler(new NioNetworkHandler())
.cryptography(BouncyCryptography.INSTANCE)
.cryptography(new BouncyCryptography())
.port(48444);
if (options.localPort != null) {
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.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.networking.DefaultNetworkHandler;
import ch.dissem.bitmessage.networking.nio.NioNetworkHandler;
import ch.dissem.bitmessage.ports.DefaultLabeler;
import ch.dissem.bitmessage.ports.Labeler;
@ -29,14 +28,10 @@ import ch.dissem.bitmessage.utils.TTL;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.mockito.Mockito;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@ -49,74 +44,62 @@ import static org.mockito.ArgumentMatchers.any;
/**
* @author Christian Basler
*/
@RunWith(Parameterized.class)
public class SystemTest {
private static int port = 6000;
private final NetworkHandler aliceNetworkHandler;
private final NetworkHandler bobNetworkHandler;
private BitmessageContext alice;
private TestListener aliceListener = new TestListener();
private Labeler aliceLabeler = Mockito.spy(new DebugLabeler("Alice"));
private BitmessageAddress aliceIdentity;
private Labeler aliceLabeler;
private BitmessageContext bob;
private TestListener bobListener = new TestListener();
private TestListener bobListener;
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
public void setUp() {
int alicePort = port++;
int bobPort = port++;
TTL.msg(5 * MINUTE);
TTL.getpubkey(5 * MINUTE);
TTL.pubkey(5 * MINUTE);
JdbcConfig aliceDB = new JdbcConfig("jdbc:h2:mem:alice;DB_CLOSE_DELAY=-1", "sa", "");
alice = new BitmessageContext.Builder()
.addressRepo(new JdbcAddressRepository(aliceDB))
.inventory(new JdbcInventory(aliceDB))
.messageRepo(new JdbcMessageRepository(aliceDB))
.powRepo(new JdbcProofOfWorkRepository(aliceDB))
.port(alicePort)
.nodeRegistry(new TestNodeRegistry(bobPort))
.networkHandler(aliceNetworkHandler)
.cryptography(BouncyCryptography.INSTANCE)
.listener(aliceListener)
.labeler(aliceLabeler)
.build();
alice.startup();
aliceIdentity = alice.createIdentity(false, DOES_ACK);
JdbcConfig bobDB = new JdbcConfig("jdbc:h2:mem:bob;DB_CLOSE_DELAY=-1", "sa", "");
bob = new BitmessageContext.Builder()
.addressRepo(new JdbcAddressRepository(bobDB))
.inventory(new JdbcInventory(bobDB))
.messageRepo(new JdbcMessageRepository(bobDB))
.powRepo(new JdbcProofOfWorkRepository(bobDB))
.port(bobPort)
.nodeRegistry(new TestNodeRegistry(alicePort))
.networkHandler(bobNetworkHandler)
.cryptography(BouncyCryptography.INSTANCE)
.listener(bobListener)
.labeler(new DebugLabeler("Bob"))
.build();
bob.startup();
bobIdentity = bob.createIdentity(false, DOES_ACK);
int alicePort = port++;
int bobPort = port++;
{
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()
.addressRepo(new JdbcAddressRepository(aliceDB))
.inventory(new JdbcInventory(aliceDB))
.messageRepo(new JdbcMessageRepository(aliceDB))
.powRepo(new JdbcProofOfWorkRepository(aliceDB))
.port(alicePort)
.nodeRegistry(new TestNodeRegistry(bobPort))
.networkHandler(new NioNetworkHandler())
.cryptography(new BouncyCryptography())
.listener(aliceListener)
.labeler(aliceLabeler)
.build();
alice.startup();
aliceIdentity = alice.createIdentity(false, DOES_ACK);
}
{
JdbcConfig bobDB = new JdbcConfig("jdbc:h2:mem:bob;DB_CLOSE_DELAY=-1", "sa", "");
bobListener = new TestListener();
bob = new BitmessageContext.Builder()
.addressRepo(new JdbcAddressRepository(bobDB))
.inventory(new JdbcInventory(bobDB))
.messageRepo(new JdbcMessageRepository(bobDB))
.powRepo(new JdbcProofOfWorkRepository(bobDB))
.port(bobPort)
.nodeRegistry(new TestNodeRegistry(alicePort))
.networkHandler(new NioNetworkHandler())
.cryptography(new BouncyCryptography())
.listener(bobListener)
.labeler(new DebugLabeler("Bob"))
.build();
bob.startup();
bobIdentity = bob.createIdentity(false, DOES_ACK);
}
((DebugLabeler) alice.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.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.encryptionKey, 1, 64)
if (identity.version >= 3) {
@ -114,7 +114,7 @@ class CryptoCustomMessage<T : Streamable> : CustomMessage {
container?.write(out) ?: throw IllegalStateException("not encrypted yet")
}
interface Reader<T> {
interface Reader<out 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.NodeException;
import ch.dissem.bitmessage.factory.V3MessageReader;
import ch.dissem.bitmessage.networking.AbstractConnection;
import ch.dissem.bitmessage.ports.NetworkHandler;
import ch.dissem.bitmessage.utils.DebugUtils;
import ch.dissem.bitmessage.utils.Property;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -75,8 +75,9 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex
private Thread starter;
@NotNull
@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>() {
@Override
public Void call() throws Exception {
@ -97,8 +98,9 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex
});
}
@NotNull
@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))) {
channel.configureBlocking(true);
ByteBuffer headerBuffer = ByteBuffer.allocate(HEADER_SIZE);
@ -129,7 +131,7 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex
if (networkMessage != null && networkMessage.getPayload() instanceof CustomMessage) {
return (CustomMessage) networkMessage.getPayload();
} else {
if (networkMessage == null || networkMessage.getPayload() == null) {
if (networkMessage == null) {
throw new NodeException("Empty response from node " + server);
} else {
throw new NodeException("Unexpected response from node " + server + ": "
@ -391,7 +393,7 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex
}
@Override
public void offer(InventoryVector iv) {
public void offer(@NotNull InventoryVector iv) {
List<ConnectionInfo> target = new LinkedList<>();
for (ConnectionInfo connection : connections.keySet()) {
if (connection.getState() == ACTIVE && !connection.knowsOf(iv)) {
@ -405,7 +407,7 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex
}
@Override
public void request(Collection<InventoryVector> inventoryVectors) {
public void request(@NotNull Collection<InventoryVector> inventoryVectors) {
if (!isRunning()) {
requestedObjects.clear();
return;
@ -468,6 +470,7 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex
}
}
@NotNull
@Override
public Property getNetworkStatus() {
TreeSet<Long> streams = new TreeSet<>();
@ -520,7 +523,7 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex
}
@Override
public void setContext(InternalContext context) {
public void setContext(@NotNull InternalContext context) {
this.ctx = context;
}
}

View File

@ -98,7 +98,7 @@ public class NetworkHandlerTest {
.port(peerAddress.getPort())
.nodeRegistry(new TestNodeRegistry())
.networkHandler(peerNetworkHandler)
.cryptography(BouncyCryptography.INSTANCE)
.cryptography(new BouncyCryptography())
.listener(mock(BitmessageContext.Listener.class))
.customCommandHandler(new CustomCommandHandler() {
@Override
@ -133,7 +133,7 @@ public class NetworkHandlerTest {
.port(6002)
.nodeRegistry(new TestNodeRegistry(peerAddress))
.networkHandler(nodeNetworkHandler)
.cryptography(BouncyCryptography.INSTANCE)
.cryptography(new BouncyCryptography())
.listener(mock(BitmessageContext.Listener.class))
.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.V4Pubkey;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.exception.ApplicationException;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.ports.AddressRepository;
import org.slf4j.Logger;
@ -137,16 +138,14 @@ public class JdbcAddressRepository extends JdbcHelper implements AddressReposito
try (
Connection connection = config.getConnection();
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM Address " +
ResultSet rs = stmt.executeQuery("SELECT '1' FROM Address " +
"WHERE address='" + address.getAddress() + "'")
) {
if (rs.next()) {
return rs.getInt(1) > 0;
}
return rs.next();
} catch (SQLException e) {
LOG.error(e.getMessage(), e);
throw new ApplicationException(e);
}
return false;
}
@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) " +
"VALUES (?, ?, ?, ?, ?, ?, ?)")
) {
ps.setBytes(1, cryptography().getInitialHash(item.getObject()));
writeBlob(ps, 2, item.getObject());
ps.setLong(3, item.getObject().getVersion());
ps.setBytes(1, cryptography().getInitialHash(item.getObjectMessage()));
writeBlob(ps, 2, item.getObjectMessage());
ps.setLong(3, item.getObjectMessage().getVersion());
ps.setLong(4, item.getNonceTrialsPerByte());
ps.setLong(5, item.getExtraBytes());
@ -120,7 +120,7 @@ public class JdbcProofOfWorkRepository extends JdbcHelper implements ProofOfWork
}
ps.executeUpdate();
} 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);
}
}

View File

@ -129,7 +129,7 @@ class JdbcProofOfWorkRepositoryTest : TestBase() {
fun `ensure item can be retrieved`() {
val item = repo.getItem(initialHash1)
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.extraBytes, `is`(1000L))
}
@ -138,7 +138,7 @@ class JdbcProofOfWorkRepositoryTest : TestBase() {
fun `ensure ack item can be retrieved`() {
val item = repo.getItem(initialHash2)
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.extraBytes, `is`(1000L))
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.TestUtils.mockedInternalContext
/**
* Created by chris on 20.07.15.
*/
open class TestBase {
companion object {
init {
val security = BouncyCryptography()
Singleton.initialize(security)
mockedInternalContext(
cryptography = security,
cryptography = BouncyCryptography(),
proofOfWorkEngine = MultiThreadedPOWEngine()
)
}

View File

@ -15,5 +15,6 @@ dependencies {
compile 'org.ini4j:ini4j:0.5.4'
testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-core:2.7.21'
testCompile 'com.nhaarman:mockito-kotlin:1.4.0'
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)
}
}