Fixes and improvements, SystemTest still broken
This commit is contained in:
parent
1d3340a547
commit
894e0ff724
@ -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();
|
||||
```
|
||||
|
@ -29,6 +29,7 @@ subprojects {
|
||||
}
|
||||
dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
|
||||
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
}
|
||||
|
||||
test {
|
||||
|
@ -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 {
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
||||
|
@ -81,6 +81,6 @@ interface NetworkHandler {
|
||||
|
||||
interface MessageListener {
|
||||
@Throws(IOException::class)
|
||||
fun receive(`object`: ObjectMessage)
|
||||
fun receive(objectMessage: ObjectMessage)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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))
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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!!))
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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() {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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() }
|
||||
|
@ -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])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -163,7 +163,7 @@ class CryptographyTest {
|
||||
val TEST_RIPEMD160 = DatatypeConverter.parseHexBinary(""
|
||||
+ "cd566972b5e50104011a92b59fa8e0b1234851ae")
|
||||
|
||||
private val crypto = BouncyCryptography
|
||||
private val crypto = BouncyCryptography()
|
||||
|
||||
init {
|
||||
Singleton.initialize(crypto)
|
||||
|
@ -40,8 +40,7 @@ import java.util.*
|
||||
* As Spongycastle can't be used on the Oracle JVM, and Bouncycastle doesn't work properly on Android (thanks, Google),
|
||||
* 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")
|
||||
}
|
||||
}
|
||||
|
@ -162,7 +162,7 @@ class CryptographyTest {
|
||||
val TEST_RIPEMD160 = DatatypeConverter.parseHexBinary(""
|
||||
+ "cd566972b5e50104011a92b59fa8e0b1234851ae")
|
||||
|
||||
private val crypto = SpongyCryptography
|
||||
private val crypto = SpongyCryptography()
|
||||
|
||||
init {
|
||||
Singleton.initialize(crypto)
|
||||
|
@ -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() {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -1,344 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.repository;
|
||||
|
||||
import ch.dissem.bitmessage.entity.Plaintext;
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
||||
import ch.dissem.bitmessage.entity.valueobject.Label;
|
||||
import ch.dissem.bitmessage.exception.ApplicationException;
|
||||
import ch.dissem.bitmessage.ports.AbstractMessageRepository;
|
||||
import ch.dissem.bitmessage.ports.MessageRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.sql.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static ch.dissem.bitmessage.repository.JdbcHelper.writeBlob;
|
||||
|
||||
public class JdbcMessageRepository extends AbstractMessageRepository implements MessageRepository {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JdbcMessageRepository.class);
|
||||
|
||||
private final JdbcConfig config;
|
||||
|
||||
public JdbcMessageRepository(JdbcConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Label> findLabels(String where) {
|
||||
try (
|
||||
Connection connection = config.getConnection()
|
||||
) {
|
||||
return findLabels(connection, where);
|
||||
} catch (SQLException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
private Label getLabel(ResultSet rs) throws SQLException {
|
||||
String typeName = rs.getString("type");
|
||||
Label.Type type = null;
|
||||
if (typeName != null) {
|
||||
type = Label.Type.valueOf(typeName);
|
||||
}
|
||||
Label label = new Label(rs.getString("label"), type, rs.getInt("color"));
|
||||
label.setId(rs.getLong("id"));
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countUnread(Label label) {
|
||||
String where;
|
||||
if (label == null) {
|
||||
where = "";
|
||||
} else {
|
||||
where = "id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId() + ") AND ";
|
||||
}
|
||||
where += "id IN (SELECT message_id FROM Message_Label WHERE label_id IN (" +
|
||||
"SELECT id FROM Label WHERE type = '" + Label.Type.UNREAD.name() + "'))";
|
||||
|
||||
try (
|
||||
Connection connection = config.getConnection();
|
||||
Statement stmt = connection.createStatement();
|
||||
ResultSet rs = stmt.executeQuery("SELECT count(*) FROM Message WHERE " + where)
|
||||
) {
|
||||
if (rs.next()) {
|
||||
return rs.getInt(1);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Plaintext> find(String where) {
|
||||
List<Plaintext> result = new LinkedList<>();
|
||||
try (
|
||||
Connection connection = config.getConnection();
|
||||
Statement stmt = connection.createStatement();
|
||||
ResultSet rs = stmt.executeQuery(
|
||||
"SELECT id, iv, type, sender, recipient, data, ack_data, sent, received, initial_hash, status, ttl, retries, next_try, conversation " +
|
||||
"FROM Message WHERE " + where)
|
||||
) {
|
||||
while (rs.next()) {
|
||||
byte[] iv = rs.getBytes("iv");
|
||||
InputStream data = rs.getBinaryStream("data");
|
||||
Plaintext.Type type = Plaintext.Type.valueOf(rs.getString("type"));
|
||||
Plaintext.Builder builder = Plaintext.readWithoutSignature(type, data);
|
||||
long id = rs.getLong("id");
|
||||
builder.id(id);
|
||||
builder.IV(InventoryVector.fromHash(iv));
|
||||
builder.from(getCtx().getAddressRepository().getAddress(rs.getString("sender")));
|
||||
builder.to(getCtx().getAddressRepository().getAddress(rs.getString("recipient")));
|
||||
builder.ackData(rs.getBytes("ack_data"));
|
||||
builder.sent(rs.getObject("sent", Long.class));
|
||||
builder.received(rs.getObject("received", Long.class));
|
||||
builder.status(Plaintext.Status.valueOf(rs.getString("status")));
|
||||
builder.ttl(rs.getLong("ttl"));
|
||||
builder.retries(rs.getInt("retries"));
|
||||
builder.nextTry(rs.getObject("next_try", Long.class));
|
||||
builder.conversation(rs.getObject("conversation", UUID.class));
|
||||
builder.labels(findLabels(connection,
|
||||
"id IN (SELECT label_id FROM Message_Label WHERE message_id=" + id + ") ORDER BY ord"));
|
||||
Plaintext message = builder.build();
|
||||
message.setInitialHash(rs.getBytes("initial_hash"));
|
||||
result.add(message);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<Label> findLabels(Connection connection, String where) {
|
||||
List<Label> result = new ArrayList<>();
|
||||
try (
|
||||
Statement stmt = connection.createStatement();
|
||||
ResultSet rs = stmt.executeQuery("SELECT id, label, type, color FROM Label WHERE " + where)
|
||||
) {
|
||||
while (rs.next()) {
|
||||
result.add(getLabel(rs));
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(Plaintext message) {
|
||||
saveContactIfNecessary(message.getFrom());
|
||||
saveContactIfNecessary(message.getTo());
|
||||
|
||||
try (Connection connection = config.getConnection()) {
|
||||
try {
|
||||
connection.setAutoCommit(false);
|
||||
save(connection, message);
|
||||
updateParents(connection, message);
|
||||
updateLabels(connection, message);
|
||||
connection.commit();
|
||||
} catch (IOException | SQLException e) {
|
||||
connection.rollback();
|
||||
throw e;
|
||||
}
|
||||
} catch (IOException | SQLException e) {
|
||||
throw new ApplicationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void save(Connection connection, Plaintext message) throws IOException, SQLException {
|
||||
if (message.getId() == null) {
|
||||
insert(connection, message);
|
||||
} else {
|
||||
update(connection, message);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateLabels(Connection connection, Plaintext message) throws SQLException {
|
||||
// remove existing labels
|
||||
try (Statement stmt = connection.createStatement()) {
|
||||
stmt.executeUpdate("DELETE FROM Message_Label WHERE message_id=" + message.getId());
|
||||
}
|
||||
// save new labels
|
||||
try (PreparedStatement ps = connection.prepareStatement("INSERT INTO Message_Label VALUES (" +
|
||||
message.getId() + ", ?)")) {
|
||||
for (Label label : message.getLabels()) {
|
||||
ps.setLong(1, (Long) label.getId());
|
||||
ps.executeUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateParents(Connection connection, Plaintext message) throws SQLException {
|
||||
if (message.getInventoryVector() == null || message.getParents().isEmpty()) {
|
||||
// There are no parents to save yet (they are saved in the extended data, that's enough for now)
|
||||
return;
|
||||
}
|
||||
// remove existing parents
|
||||
try (PreparedStatement ps = connection.prepareStatement("DELETE FROM Message_Parent WHERE child=?")) {
|
||||
ps.setBytes(1, message.getInitialHash());
|
||||
ps.executeUpdate();
|
||||
}
|
||||
byte[] childIV = message.getInventoryVector().getHash();
|
||||
// save new parents
|
||||
int order = 0;
|
||||
try (PreparedStatement ps = connection.prepareStatement("INSERT INTO Message_Parent VALUES (?, ?, ?, ?)")) {
|
||||
for (InventoryVector parentIV : message.getParents()) {
|
||||
Plaintext parent = getMessage(parentIV);
|
||||
mergeConversations(connection, parent.getConversationId(), message.getConversationId());
|
||||
order++;
|
||||
ps.setBytes(1, parentIV.getHash());
|
||||
ps.setBytes(2, childIV);
|
||||
ps.setInt(3, order); // FIXME: this might not be necessary
|
||||
ps.setObject(4, message.getConversationId());
|
||||
ps.executeUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void insert(Connection connection, Plaintext message) throws SQLException, IOException {
|
||||
try (PreparedStatement ps = connection.prepareStatement(
|
||||
"INSERT INTO Message (iv, type, sender, recipient, data, ack_data, sent, received, " +
|
||||
"status, initial_hash, ttl, retries, next_try, conversation) " +
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
Statement.RETURN_GENERATED_KEYS)
|
||||
) {
|
||||
ps.setBytes(1, message.getInventoryVector() == null ? null : message.getInventoryVector().getHash());
|
||||
ps.setString(2, message.getType().name());
|
||||
ps.setString(3, message.getFrom().getAddress());
|
||||
ps.setString(4, message.getTo() == null ? null : message.getTo().getAddress());
|
||||
writeBlob(ps, 5, message);
|
||||
ps.setBytes(6, message.getAckData());
|
||||
ps.setObject(7, message.getSent());
|
||||
ps.setObject(8, message.getReceived());
|
||||
ps.setString(9, message.getStatus() == null ? null : message.getStatus().name());
|
||||
ps.setBytes(10, message.getInitialHash());
|
||||
ps.setLong(11, message.getTTL());
|
||||
ps.setInt(12, message.getRetries());
|
||||
ps.setObject(13, message.getNextTry());
|
||||
ps.setObject(14, message.getConversationId());
|
||||
|
||||
ps.executeUpdate();
|
||||
// get generated id
|
||||
try (ResultSet rs = ps.getGeneratedKeys()) {
|
||||
rs.next();
|
||||
message.setId(rs.getLong(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void update(Connection connection, Plaintext message) throws SQLException, IOException {
|
||||
try (PreparedStatement ps = connection.prepareStatement(
|
||||
"UPDATE Message SET iv=?, type=?, sender=?, recipient=?, data=?, ack_data=?, sent=?, received=?, " +
|
||||
"status=?, initial_hash=?, ttl=?, retries=?, next_try=? " +
|
||||
"WHERE id=?")) {
|
||||
ps.setBytes(1, message.getInventoryVector() == null ? null : message.getInventoryVector().getHash());
|
||||
ps.setString(2, message.getType().name());
|
||||
ps.setString(3, message.getFrom().getAddress());
|
||||
ps.setString(4, message.getTo() == null ? null : message.getTo().getAddress());
|
||||
writeBlob(ps, 5, message);
|
||||
ps.setBytes(6, message.getAckData());
|
||||
ps.setObject(7, message.getSent());
|
||||
ps.setObject(8, message.getReceived());
|
||||
ps.setString(9, message.getStatus() == null ? null : message.getStatus().name());
|
||||
ps.setBytes(10, message.getInitialHash());
|
||||
ps.setLong(11, message.getTTL());
|
||||
ps.setInt(12, message.getRetries());
|
||||
ps.setObject(13, message.getNextTry());
|
||||
ps.setLong(14, (Long) message.getId());
|
||||
ps.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Plaintext message) {
|
||||
try (Connection connection = config.getConnection()) {
|
||||
connection.setAutoCommit(false);
|
||||
try (Statement stmt = connection.createStatement()) {
|
||||
stmt.executeUpdate("DELETE FROM Message_Label WHERE message_id = " + message.getId());
|
||||
stmt.executeUpdate("DELETE FROM Message WHERE id = " + message.getId());
|
||||
connection.commit();
|
||||
} catch (SQLException e) {
|
||||
try {
|
||||
connection.rollback();
|
||||
} catch (SQLException e1) {
|
||||
LOG.debug(e1.getMessage(), e);
|
||||
}
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UUID> findConversations(Label label) {
|
||||
String where;
|
||||
if (label == null) {
|
||||
where = "id NOT IN (SELECT message_id FROM Message_Label)";
|
||||
} else {
|
||||
where = "id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId() + ")";
|
||||
}
|
||||
List<UUID> result = new LinkedList<>();
|
||||
try (
|
||||
Connection connection = config.getConnection();
|
||||
Statement stmt = connection.createStatement();
|
||||
ResultSet rs = stmt.executeQuery(
|
||||
"SELECT DISTINCT conversation FROM Message WHERE " + where)
|
||||
) {
|
||||
while (rs.next()) {
|
||||
result.add((UUID) rs.getObject(1));
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces every occurrence of the source conversation ID with the target ID
|
||||
*
|
||||
* @param source ID of the conversation to be merged
|
||||
* @param target ID of the merge target
|
||||
*/
|
||||
private void mergeConversations(Connection connection, UUID source, UUID target) {
|
||||
try (
|
||||
PreparedStatement ps1 = connection.prepareStatement(
|
||||
"UPDATE Message SET conversation=? WHERE conversation=?");
|
||||
PreparedStatement ps2 = connection.prepareStatement(
|
||||
"UPDATE Message_Parent SET conversation=? WHERE conversation=?")
|
||||
) {
|
||||
ps1.setObject(1, target);
|
||||
ps1.setObject(2, source);
|
||||
ps1.executeUpdate();
|
||||
ps2.setObject(1, target);
|
||||
ps2.setObject(2, source);
|
||||
ps2.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,344 @@
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.repository
|
||||
|
||||
import ch.dissem.bitmessage.entity.Plaintext
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
||||
import ch.dissem.bitmessage.entity.valueobject.Label
|
||||
import ch.dissem.bitmessage.ports.AbstractMessageRepository
|
||||
import ch.dissem.bitmessage.ports.MessageRepository
|
||||
import ch.dissem.bitmessage.repository.JdbcHelper.writeBlob
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.IOException
|
||||
import java.sql.Connection
|
||||
import java.sql.ResultSet
|
||||
import java.sql.SQLException
|
||||
import java.sql.Statement
|
||||
import java.util.*
|
||||
|
||||
class JdbcMessageRepository(private val config: JdbcConfig) : AbstractMessageRepository(), MessageRepository {
|
||||
|
||||
override fun findLabels(where: String): List<Label> {
|
||||
try {
|
||||
config.connection.use {
|
||||
connection ->
|
||||
return findLabels(connection, where)
|
||||
}
|
||||
} catch (e: SQLException) {
|
||||
LOG.error(e.message, e)
|
||||
return ArrayList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLabel(rs: ResultSet): Label {
|
||||
val typeName = rs.getString("type")
|
||||
val type = if (typeName == null) {
|
||||
null
|
||||
} else {
|
||||
Label.Type.valueOf(typeName)
|
||||
}
|
||||
val label = Label(rs.getString("label"), type, rs.getInt("color"))
|
||||
label.id = rs.getLong("id")
|
||||
|
||||
return label
|
||||
}
|
||||
|
||||
override fun countUnread(label: Label?): Int {
|
||||
val where = if (label == null) {
|
||||
""
|
||||
} else {
|
||||
"id IN (SELECT message_id FROM Message_Label WHERE label_id=${label.id}) AND "
|
||||
} + "id IN (SELECT message_id FROM Message_Label WHERE label_id IN (" +
|
||||
"SELECT id FROM Label WHERE type = '" + Label.Type.UNREAD.name + "'))"
|
||||
|
||||
try {
|
||||
config.connection.use { connection ->
|
||||
connection.createStatement().use { stmt ->
|
||||
stmt.executeQuery("SELECT count(*) FROM Message WHERE $where").use { rs ->
|
||||
if (rs.next()) {
|
||||
return rs.getInt(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: SQLException) {
|
||||
LOG.error(e.message, e)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun find(where: String): List<Plaintext> {
|
||||
val result = LinkedList<Plaintext>()
|
||||
try {
|
||||
config.connection.use { connection ->
|
||||
connection.createStatement().use { stmt ->
|
||||
stmt.executeQuery(
|
||||
"""SELECT id, iv, type, sender, recipient, data, ack_data, sent, received, initial_hash, status, ttl, retries, next_try, conversation
|
||||
FROM Message WHERE $where""").use { rs ->
|
||||
while (rs.next()) {
|
||||
val iv = rs.getBytes("iv")
|
||||
val data = rs.getBinaryStream("data")
|
||||
val type = Plaintext.Type.valueOf(rs.getString("type"))
|
||||
val builder = Plaintext.readWithoutSignature(type, data)
|
||||
val id = rs.getLong("id")
|
||||
builder.id(id)
|
||||
builder.IV(InventoryVector.fromHash(iv))
|
||||
builder.from(ctx.addressRepository.getAddress(rs.getString("sender"))!!)
|
||||
builder.to(ctx.addressRepository.getAddress(rs.getString("recipient")))
|
||||
builder.ackData(rs.getBytes("ack_data"))
|
||||
builder.sent(rs.getObject("sent", Long::class.java))
|
||||
builder.received(rs.getObject("received", Long::class.java))
|
||||
builder.status(Plaintext.Status.valueOf(rs.getString("status")))
|
||||
builder.ttl(rs.getLong("ttl"))
|
||||
builder.retries(rs.getInt("retries"))
|
||||
builder.nextTry(rs.getObject("next_try", Long::class.java))
|
||||
builder.conversation(rs.getObject("conversation", UUID::class.java))
|
||||
builder.labels(findLabels(connection,
|
||||
"id IN (SELECT label_id FROM Message_Label WHERE message_id=$id) ORDER BY ord"))
|
||||
val message = builder.build()
|
||||
message.initialHash = rs.getBytes("initial_hash")
|
||||
result.add(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: SQLException) {
|
||||
LOG.error(e.message, e)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun findLabels(connection: Connection, where: String): List<Label> {
|
||||
val result = ArrayList<Label>()
|
||||
try {
|
||||
connection.createStatement().use { stmt ->
|
||||
stmt.executeQuery("SELECT id, label, type, color FROM Label WHERE $where").use { rs ->
|
||||
while (rs.next()) {
|
||||
result.add(getLabel(rs))
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: SQLException) {
|
||||
LOG.error(e.message, e)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
override fun save(message: Plaintext) {
|
||||
saveContactIfNecessary(message.from)
|
||||
saveContactIfNecessary(message.to)
|
||||
|
||||
config.connection.use { connection ->
|
||||
try {
|
||||
connection.autoCommit = false
|
||||
save(connection, message)
|
||||
updateParents(connection, message)
|
||||
updateLabels(connection, message)
|
||||
connection.commit()
|
||||
} catch (e: Exception) {
|
||||
connection.rollback()
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun save(connection: Connection, message: Plaintext) {
|
||||
if (message.id == null) {
|
||||
insert(connection, message)
|
||||
} else {
|
||||
update(connection, message)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateLabels(connection: Connection, message: Plaintext) {
|
||||
// remove existing labels
|
||||
connection.createStatement().use { stmt -> stmt.executeUpdate("DELETE FROM Message_Label WHERE message_id=${message.id!!}") }
|
||||
// save new labels
|
||||
connection.prepareStatement("INSERT INTO Message_Label VALUES (${message.id}, ?)").use { ps ->
|
||||
for (label in message.labels) {
|
||||
ps.setLong(1, (label.id as Long?)!!)
|
||||
ps.executeUpdate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateParents(connection: Connection, message: Plaintext) {
|
||||
if (message.inventoryVector == null || message.parents.isEmpty()) {
|
||||
// There are no parents to save yet (they are saved in the extended data, that's enough for now)
|
||||
return
|
||||
}
|
||||
// remove existing parents
|
||||
connection.prepareStatement("DELETE FROM Message_Parent WHERE child=?").use { ps ->
|
||||
ps.setBytes(1, message.initialHash)
|
||||
ps.executeUpdate()
|
||||
}
|
||||
val childIV = message.inventoryVector!!.hash
|
||||
// save new parents
|
||||
var order = 0
|
||||
connection.prepareStatement("INSERT INTO Message_Parent VALUES (?, ?, ?, ?)").use { ps ->
|
||||
for (parentIV in message.parents) {
|
||||
val parent = getMessage(parentIV)
|
||||
mergeConversations(connection, parent!!.conversationId, message.conversationId)
|
||||
order++
|
||||
ps.setBytes(1, parentIV.hash)
|
||||
ps.setBytes(2, childIV)
|
||||
ps.setInt(3, order) // FIXME: this might not be necessary
|
||||
ps.setObject(4, message.conversationId)
|
||||
ps.executeUpdate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun insert(connection: Connection, message: Plaintext) {
|
||||
connection.prepareStatement(
|
||||
"INSERT INTO Message (iv, type, sender, recipient, data, ack_data, sent, received, " +
|
||||
"status, initial_hash, ttl, retries, next_try, conversation) " +
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
Statement.RETURN_GENERATED_KEYS).use { ps ->
|
||||
ps.setBytes(1, if (message.inventoryVector == null) null else message.inventoryVector!!.hash)
|
||||
ps.setString(2, message.type.name)
|
||||
ps.setString(3, message.from.address)
|
||||
ps.setString(4, if (message.to == null) null else message.to!!.address)
|
||||
writeBlob(ps, 5, message)
|
||||
ps.setBytes(6, message.ackData)
|
||||
ps.setObject(7, message.sent)
|
||||
ps.setObject(8, message.received)
|
||||
ps.setString(9, message.status.name)
|
||||
ps.setBytes(10, message.initialHash)
|
||||
ps.setLong(11, message.ttl)
|
||||
ps.setInt(12, message.retries)
|
||||
ps.setObject(13, message.nextTry)
|
||||
ps.setObject(14, message.conversationId)
|
||||
|
||||
ps.executeUpdate()
|
||||
// get generated id
|
||||
ps.generatedKeys.use { rs ->
|
||||
rs.next()
|
||||
message.id = rs.getLong(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(SQLException::class, IOException::class)
|
||||
private fun update(connection: Connection, message: Plaintext) {
|
||||
connection.prepareStatement(
|
||||
"UPDATE Message SET iv=?, type=?, sender=?, recipient=?, data=?, ack_data=?, sent=?, received=?, " +
|
||||
"status=?, initial_hash=?, ttl=?, retries=?, next_try=? " +
|
||||
"WHERE id=?").use { ps ->
|
||||
ps.setBytes(1, if (message.inventoryVector == null) null else message.inventoryVector!!.hash)
|
||||
ps.setString(2, message.type.name)
|
||||
ps.setString(3, message.from.address)
|
||||
ps.setString(4, if (message.to == null) null else message.to!!.address)
|
||||
writeBlob(ps, 5, message)
|
||||
ps.setBytes(6, message.ackData)
|
||||
ps.setObject(7, message.sent)
|
||||
ps.setObject(8, message.received)
|
||||
ps.setString(9, message.status.name)
|
||||
ps.setBytes(10, message.initialHash)
|
||||
ps.setLong(11, message.ttl)
|
||||
ps.setInt(12, message.retries)
|
||||
ps.setObject(13, message.nextTry)
|
||||
ps.setLong(14, (message.id as Long?)!!)
|
||||
ps.executeUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
override fun remove(message: Plaintext) {
|
||||
try {
|
||||
config.connection.use { connection ->
|
||||
connection.autoCommit = false
|
||||
try {
|
||||
connection.createStatement().use { stmt ->
|
||||
stmt.executeUpdate("DELETE FROM Message_Label WHERE message_id = " + message.id!!)
|
||||
stmt.executeUpdate("DELETE FROM Message WHERE id = " + message.id!!)
|
||||
connection.commit()
|
||||
}
|
||||
} catch (e: SQLException) {
|
||||
try {
|
||||
connection.rollback()
|
||||
} catch (e1: SQLException) {
|
||||
LOG.debug(e1.message, e)
|
||||
}
|
||||
|
||||
LOG.error(e.message, e)
|
||||
}
|
||||
}
|
||||
} catch (e: SQLException) {
|
||||
LOG.error(e.message, e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun findConversations(label: Label?): List<UUID> {
|
||||
val where: String
|
||||
if (label == null) {
|
||||
where = "id NOT IN (SELECT message_id FROM Message_Label)"
|
||||
} else {
|
||||
where = "id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.id + ")"
|
||||
}
|
||||
val result = LinkedList<UUID>()
|
||||
try {
|
||||
config.connection.use { connection ->
|
||||
connection.createStatement().use { stmt ->
|
||||
stmt.executeQuery(
|
||||
"SELECT DISTINCT conversation FROM Message WHERE " + where).use { rs ->
|
||||
while (rs.next()) {
|
||||
result.add(rs.getObject(1) as UUID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: SQLException) {
|
||||
LOG.error(e.message, e)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces every occurrence of the source conversation ID with the target ID
|
||||
|
||||
* @param source ID of the conversation to be merged
|
||||
* *
|
||||
* @param target ID of the merge target
|
||||
*/
|
||||
private fun mergeConversations(connection: Connection, source: UUID, target: UUID) {
|
||||
try {
|
||||
connection.prepareStatement(
|
||||
"UPDATE Message SET conversation=? WHERE conversation=?").use { ps1 ->
|
||||
connection.prepareStatement(
|
||||
"UPDATE Message_Parent SET conversation=? WHERE conversation=?").use { ps2 ->
|
||||
ps1.setObject(1, target)
|
||||
ps1.setObject(2, source)
|
||||
ps1.executeUpdate()
|
||||
ps2.setObject(1, target)
|
||||
ps2.setObject(2, source)
|
||||
ps2.executeUpdate()
|
||||
}
|
||||
}
|
||||
} catch (e: SQLException) {
|
||||
LOG.error(e.message, e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOG = LoggerFactory.getLogger(JdbcMessageRepository::class.java)
|
||||
}
|
||||
}
|
@ -105,9 +105,9 @@ public class JdbcProofOfWorkRepository extends JdbcHelper implements ProofOfWork
|
||||
"nonce_trials_per_byte, extra_bytes, expiration_time, message_id) " +
|
||||
"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);
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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()
|
||||
)
|
||||
}
|
||||
|
@ -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')
|
||||
}
|
||||
|
@ -1,106 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.wif;
|
||||
|
||||
import ch.dissem.bitmessage.BitmessageContext;
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.exception.ApplicationException;
|
||||
import ch.dissem.bitmessage.utils.Base58;
|
||||
import org.ini4j.Ini;
|
||||
import org.ini4j.Profile;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Collection;
|
||||
|
||||
import static ch.dissem.bitmessage.entity.valueobject.PrivateKey.PRIVATE_KEY_SIZE;
|
||||
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
|
||||
|
||||
/**
|
||||
* @author Christian Basler
|
||||
*/
|
||||
public class WifExporter {
|
||||
private final BitmessageContext ctx;
|
||||
private final Ini ini;
|
||||
|
||||
public WifExporter(BitmessageContext ctx) {
|
||||
this.ctx = ctx;
|
||||
this.ini = new Ini();
|
||||
}
|
||||
|
||||
public WifExporter addAll() {
|
||||
for (BitmessageAddress identity : ctx.addresses().getIdentities()) {
|
||||
addIdentity(identity);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public WifExporter addAll(Collection<BitmessageAddress> identities) {
|
||||
for (BitmessageAddress identity : identities) {
|
||||
addIdentity(identity);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public WifExporter addIdentity(BitmessageAddress identity) {
|
||||
Profile.Section section = ini.add(identity.getAddress());
|
||||
section.add("label", identity.getAlias());
|
||||
section.add("enabled", true);
|
||||
section.add("decoy", false);
|
||||
if (identity.isChan()) {
|
||||
section.add("chan", identity.isChan());
|
||||
}
|
||||
section.add("noncetrialsperbyte", identity.getPubkey().getNonceTrialsPerByte());
|
||||
section.add("payloadlengthextrabytes", identity.getPubkey().getExtraBytes());
|
||||
section.add("privsigningkey", exportSecret(identity.getPrivateKey().getPrivateSigningKey()));
|
||||
section.add("privencryptionkey", exportSecret(identity.getPrivateKey().getPrivateEncryptionKey()));
|
||||
return this;
|
||||
}
|
||||
|
||||
private String exportSecret(byte[] privateKey) {
|
||||
if (privateKey.length != PRIVATE_KEY_SIZE) {
|
||||
throw new IllegalArgumentException("Private key of length 32 expected, but was " + privateKey.length);
|
||||
}
|
||||
byte[] result = new byte[37];
|
||||
result[0] = (byte) 0x80;
|
||||
System.arraycopy(privateKey, 0, result, 1, PRIVATE_KEY_SIZE);
|
||||
byte[] hash = cryptography().doubleSha256(result, PRIVATE_KEY_SIZE + 1);
|
||||
System.arraycopy(hash, 0, result, PRIVATE_KEY_SIZE + 1, 4);
|
||||
return Base58.encode(result);
|
||||
}
|
||||
|
||||
public void write(File file) throws IOException {
|
||||
file.createNewFile();
|
||||
try (FileOutputStream out = new FileOutputStream(file)) {
|
||||
write(out);
|
||||
}
|
||||
}
|
||||
|
||||
public void write(OutputStream out) throws IOException {
|
||||
ini.store(out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringWriter writer = new StringWriter();
|
||||
try {
|
||||
ini.store(writer);
|
||||
} catch (IOException e) {
|
||||
throw new ApplicationException(e);
|
||||
}
|
||||
return writer.toString();
|
||||
}
|
||||
}
|
103
wif/src/main/java/ch/dissem/bitmessage/wif/WifExporter.kt
Normal file
103
wif/src/main/java/ch/dissem/bitmessage/wif/WifExporter.kt
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.wif
|
||||
|
||||
import ch.dissem.bitmessage.BitmessageContext
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey.Companion.PRIVATE_KEY_SIZE
|
||||
import ch.dissem.bitmessage.utils.Base58
|
||||
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||
import org.ini4j.Ini
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.OutputStream
|
||||
import java.io.StringWriter
|
||||
|
||||
/**
|
||||
* @author Christian Basler
|
||||
*/
|
||||
class WifExporter(private val ctx: BitmessageContext) {
|
||||
private val ini = Ini()
|
||||
|
||||
fun addAll(): WifExporter {
|
||||
ctx.addresses.getIdentities().forEach { addIdentity(it) }
|
||||
return this
|
||||
}
|
||||
|
||||
fun addAll(identities: Collection<BitmessageAddress>): WifExporter {
|
||||
identities.forEach { addIdentity(it) }
|
||||
return this
|
||||
}
|
||||
|
||||
fun addIdentity(identity: BitmessageAddress): WifExporter {
|
||||
val section = ini.add(identity.address)
|
||||
section.add("label", identity.alias)
|
||||
section.add("enabled", true)
|
||||
section.add("decoy", false)
|
||||
if (identity.isChan) {
|
||||
section.add("chan", identity.isChan)
|
||||
}
|
||||
section.add("noncetrialsperbyte", identity.pubkey!!.nonceTrialsPerByte)
|
||||
section.add("payloadlengthextrabytes", identity.pubkey!!.extraBytes)
|
||||
section.add("privsigningkey", exportSecret(identity.privateKey!!.privateSigningKey))
|
||||
section.add("privencryptionkey", exportSecret(identity.privateKey!!.privateEncryptionKey))
|
||||
return this
|
||||
}
|
||||
|
||||
private fun exportSecret(privateKey: ByteArray): String {
|
||||
if (privateKey.size != PRIVATE_KEY_SIZE) {
|
||||
throw IllegalArgumentException("Private key of length 32 expected, but was " + privateKey.size)
|
||||
}
|
||||
val result = ByteArray(37)
|
||||
result[0] = 0x80.toByte()
|
||||
System.arraycopy(privateKey, 0, result, 1, PRIVATE_KEY_SIZE)
|
||||
val hash = cryptography().doubleSha256(result, PRIVATE_KEY_SIZE + 1)
|
||||
System.arraycopy(hash, 0, result, PRIVATE_KEY_SIZE + 1, 4)
|
||||
return Base58.encode(result)
|
||||
}
|
||||
|
||||
fun write(file: File) {
|
||||
file.createNewFile()
|
||||
FileOutputStream(file).use { out -> write(out) }
|
||||
}
|
||||
|
||||
fun write(out: OutputStream) {
|
||||
ini.store(out)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
val writer = StringWriter()
|
||||
ini.store(writer)
|
||||
return writer.toString()
|
||||
}
|
||||
}
|
@ -1,119 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.wif;
|
||||
|
||||
import ch.dissem.bitmessage.BitmessageContext;
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
||||
import ch.dissem.bitmessage.factory.Factory;
|
||||
import ch.dissem.bitmessage.utils.Base58;
|
||||
import org.ini4j.Ini;
|
||||
import org.ini4j.Profile;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
|
||||
|
||||
/**
|
||||
* @author Christian Basler
|
||||
*/
|
||||
public class WifImporter {
|
||||
private static final byte WIF_FIRST_BYTE = (byte) 0x80;
|
||||
private static final int WIF_SECRET_LENGTH = 37;
|
||||
|
||||
private final BitmessageContext ctx;
|
||||
private final List<BitmessageAddress> identities = new LinkedList<>();
|
||||
|
||||
public WifImporter(BitmessageContext ctx, File file) throws IOException {
|
||||
this(ctx, new FileInputStream(file));
|
||||
}
|
||||
|
||||
public WifImporter(BitmessageContext ctx, String data) throws IOException {
|
||||
this(ctx, new ByteArrayInputStream(data.getBytes("utf-8")));
|
||||
}
|
||||
|
||||
public WifImporter(BitmessageContext ctx, InputStream in, Pubkey.Feature... features) throws IOException {
|
||||
this.ctx = ctx;
|
||||
|
||||
Ini ini = new Ini();
|
||||
ini.load(in);
|
||||
|
||||
for (Entry<String, Profile.Section> entry : ini.entrySet()) {
|
||||
if (!entry.getKey().startsWith("BM-"))
|
||||
continue;
|
||||
|
||||
Profile.Section section = entry.getValue();
|
||||
BitmessageAddress address = Factory.createIdentityFromPrivateKey(
|
||||
entry.getKey(),
|
||||
getSecret(section.get("privsigningkey")),
|
||||
getSecret(section.get("privencryptionkey")),
|
||||
Long.parseLong(section.get("noncetrialsperbyte")),
|
||||
Long.parseLong(section.get("payloadlengthextrabytes")),
|
||||
Pubkey.Feature.bitfield(features)
|
||||
);
|
||||
if (section.containsKey("chan")) {
|
||||
address.setChan(Boolean.parseBoolean(section.get("chan")));
|
||||
}
|
||||
address.setAlias(section.get("label"));
|
||||
identities.add(address);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getSecret(String walletImportFormat) throws IOException {
|
||||
byte[] bytes = Base58.decode(walletImportFormat);
|
||||
if (bytes[0] != WIF_FIRST_BYTE)
|
||||
throw new IOException("Unknown format: 0x80 expected as first byte, but secret " + walletImportFormat +
|
||||
" was " + bytes[0]);
|
||||
if (bytes.length != WIF_SECRET_LENGTH)
|
||||
throw new IOException("Unknown format: " + WIF_SECRET_LENGTH +
|
||||
" bytes expected, but secret " + walletImportFormat + " was " + bytes.length + " long");
|
||||
|
||||
byte[] hash = cryptography().doubleSha256(bytes, 33);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (hash[i] != bytes[33 + i]) throw new IOException("Hash check failed for secret " + walletImportFormat);
|
||||
}
|
||||
return Arrays.copyOfRange(bytes, 1, 33);
|
||||
}
|
||||
|
||||
public List<BitmessageAddress> getIdentities() {
|
||||
return identities;
|
||||
}
|
||||
|
||||
public WifImporter importAll() {
|
||||
for (BitmessageAddress identity : identities) {
|
||||
ctx.addresses().save(identity);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public WifImporter importAll(Collection<BitmessageAddress> identities) {
|
||||
for (BitmessageAddress identity : identities) {
|
||||
ctx.addresses().save(identity);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public WifImporter importIdentity(BitmessageAddress identity) {
|
||||
ctx.addresses().save(identity);
|
||||
return this;
|
||||
}
|
||||
}
|
126
wif/src/main/java/ch/dissem/bitmessage/wif/WifImporter.kt
Normal file
126
wif/src/main/java/ch/dissem/bitmessage/wif/WifImporter.kt
Normal file
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.wif
|
||||
|
||||
import ch.dissem.bitmessage.BitmessageContext
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey
|
||||
import ch.dissem.bitmessage.exception.ApplicationException
|
||||
import ch.dissem.bitmessage.factory.Factory
|
||||
import ch.dissem.bitmessage.utils.Base58
|
||||
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||
import org.ini4j.Ini
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* @author Christian Basler
|
||||
*/
|
||||
class WifImporter constructor(
|
||||
private val ctx: BitmessageContext,
|
||||
`in`: InputStream,
|
||||
vararg features: Pubkey.Feature
|
||||
) {
|
||||
private val identities = LinkedList<BitmessageAddress>()
|
||||
|
||||
constructor(ctx: BitmessageContext, file: File) : this(ctx, FileInputStream(file))
|
||||
|
||||
constructor(ctx: BitmessageContext, data: String) : this(ctx, ByteArrayInputStream(data.toByteArray(charset("utf-8"))))
|
||||
|
||||
init {
|
||||
val ini = Ini()
|
||||
ini.load(`in`)
|
||||
|
||||
for ((key, section) in ini) {
|
||||
if (!key.startsWith("BM-"))
|
||||
continue
|
||||
|
||||
val address = Factory.createIdentityFromPrivateKey(
|
||||
key,
|
||||
getSecret(section["privsigningkey"] ?: throw ApplicationException("privsigningkey missing for $key")),
|
||||
getSecret(section["privencryptionkey"] ?: throw ApplicationException("privencryptionkey missing for $key")),
|
||||
section["noncetrialsperbyte"]?.toLongOrNull() ?: throw ApplicationException("noncetrialsperbyte missing for $key"),
|
||||
section["payloadlengthextrabytes"]?.toLongOrNull() ?: throw ApplicationException("payloadlengthextrabytes missing for $key"),
|
||||
Pubkey.Feature.bitfield(*features)
|
||||
)
|
||||
if (section.containsKey("chan")) {
|
||||
address.isChan = java.lang.Boolean.parseBoolean(section["chan"])
|
||||
}
|
||||
address.alias = section["label"]
|
||||
identities.add(address)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSecret(walletImportFormat: String): ByteArray {
|
||||
val bytes = Base58.decode(walletImportFormat)
|
||||
if (bytes[0] != WIF_FIRST_BYTE)
|
||||
throw ApplicationException("Unknown format: 0x80 expected as first byte, but secret " + walletImportFormat +
|
||||
" was " + bytes[0])
|
||||
if (bytes.size != WIF_SECRET_LENGTH)
|
||||
throw ApplicationException("Unknown format: " + WIF_SECRET_LENGTH +
|
||||
" bytes expected, but secret " + walletImportFormat + " was " + bytes.size + " long")
|
||||
|
||||
val hash = cryptography().doubleSha256(bytes, 33)
|
||||
(0..3)
|
||||
.filter { hash[it] != bytes[33 + it] }
|
||||
.forEach { throw ApplicationException("Hash check failed for secret " + walletImportFormat) }
|
||||
return Arrays.copyOfRange(bytes, 1, 33)
|
||||
}
|
||||
|
||||
fun getIdentities(): List<BitmessageAddress> {
|
||||
return identities
|
||||
}
|
||||
|
||||
fun importAll(): WifImporter {
|
||||
identities.forEach { ctx.addresses.save(it) }
|
||||
return this
|
||||
}
|
||||
|
||||
fun importAll(identities: Collection<BitmessageAddress>): WifImporter {
|
||||
identities.forEach { ctx.addresses.save(it) }
|
||||
return this
|
||||
}
|
||||
|
||||
fun importIdentity(identity: BitmessageAddress): WifImporter {
|
||||
ctx.addresses.save(identity)
|
||||
return this
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val WIF_FIRST_BYTE = 0x80.toByte()
|
||||
private const val WIF_SECRET_LENGTH = 37
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.wif;
|
||||
|
||||
import ch.dissem.bitmessage.BitmessageContext;
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.ports.*;
|
||||
import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class WifExporterTest {
|
||||
private AddressRepository repo = mock(AddressRepository.class);
|
||||
private BitmessageContext ctx;
|
||||
private WifImporter importer;
|
||||
private WifExporter exporter;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
ctx = new BitmessageContext.Builder()
|
||||
.cryptography(BouncyCryptography.INSTANCE)
|
||||
.networkHandler(mock(NetworkHandler.class))
|
||||
.inventory(mock(Inventory.class))
|
||||
.messageRepo(mock(MessageRepository.class))
|
||||
.powRepo(mock(ProofOfWorkRepository.class))
|
||||
.nodeRegistry(mock(NodeRegistry.class))
|
||||
.addressRepo(repo)
|
||||
.build();
|
||||
importer = new WifImporter(ctx, getClass().getClassLoader().getResourceAsStream("nuked.dat"));
|
||||
assertEquals(81, importer.getIdentities().size());
|
||||
exporter = new WifExporter(ctx);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddAll() throws Exception {
|
||||
when(repo.getIdentities()).thenReturn(importer.getIdentities());
|
||||
exporter.addAll();
|
||||
String result = exporter.toString();
|
||||
int count = 0;
|
||||
for (int i = 0; i < result.length(); i++) {
|
||||
if (result.charAt(i) == '[') count++;
|
||||
}
|
||||
assertEquals(importer.getIdentities().size(), count);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddAllFromCollection() throws Exception {
|
||||
exporter.addAll(importer.getIdentities());
|
||||
String result = exporter.toString();
|
||||
int count = 0;
|
||||
for (int i = 0; i < result.length(); i++) {
|
||||
if (result.charAt(i) == '[') count++;
|
||||
}
|
||||
assertEquals(importer.getIdentities().size(), count);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddIdentity() throws Exception {
|
||||
String expected = "[BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn]" + System.lineSeparator() +
|
||||
"label = Nuked Address" + System.lineSeparator() +
|
||||
"enabled = true" + System.lineSeparator() +
|
||||
"decoy = false" + System.lineSeparator() +
|
||||
"noncetrialsperbyte = 320" + System.lineSeparator() +
|
||||
"payloadlengthextrabytes = 14000" + System.lineSeparator() +
|
||||
"privsigningkey = 5KU2gbe9u4rKJ8PHYb1rvwMnZnAJj4gtV5GLwoYckeYzygWUzB9" + System.lineSeparator() +
|
||||
"privencryptionkey = 5KHd4c6cavd8xv4kzo3PwnVaYuBgEfg7voPQ5V97aZKgpYBXGck" + System.lineSeparator() +
|
||||
System.lineSeparator();
|
||||
importer = new WifImporter(ctx, expected);
|
||||
exporter.addIdentity(importer.getIdentities().get(0));
|
||||
assertEquals(expected, exporter.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ensureChanIsAdded() throws Exception {
|
||||
String expected = "[BM-2cW67GEKkHGonXKZLCzouLLxnLym3azS8r]" + System.lineSeparator() +
|
||||
"label = general" + System.lineSeparator() +
|
||||
"enabled = true" + System.lineSeparator() +
|
||||
"decoy = false" + System.lineSeparator() +
|
||||
"chan = true" + System.lineSeparator() +
|
||||
"noncetrialsperbyte = 1000" + System.lineSeparator() +
|
||||
"payloadlengthextrabytes = 1000" + System.lineSeparator() +
|
||||
"privsigningkey = 5Jnbdwc4u4DG9ipJxYLznXSvemkRFueQJNHujAQamtDDoX3N1eQ" + System.lineSeparator() +
|
||||
"privencryptionkey = 5JrDcFtQDv5ydcHRW6dfGUEvThoxCCLNEUaxQfy8LXXgTJzVAcq" + System.lineSeparator() +
|
||||
System.lineSeparator();
|
||||
BitmessageAddress chan = ctx.joinChan("general", "BM-2cW67GEKkHGonXKZLCzouLLxnLym3azS8r");
|
||||
exporter.addIdentity(chan);
|
||||
assertEquals(expected, exporter.toString());
|
||||
}
|
||||
}
|
122
wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.kt
Normal file
122
wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.kt
Normal file
@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.wif
|
||||
|
||||
import ch.dissem.bitmessage.BitmessageContext
|
||||
import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography
|
||||
import ch.dissem.bitmessage.ports.AddressRepository
|
||||
import com.nhaarman.mockito_kotlin.mock
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class WifExporterTest {
|
||||
private val repo = mock<AddressRepository>()
|
||||
private lateinit var ctx: BitmessageContext
|
||||
private lateinit var importer: WifImporter
|
||||
private lateinit var exporter: WifExporter
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
ctx = BitmessageContext.Builder()
|
||||
.cryptography(BouncyCryptography())
|
||||
.networkHandler(mock())
|
||||
.inventory(mock())
|
||||
.messageRepo(mock())
|
||||
.powRepo(mock())
|
||||
.nodeRegistry(mock())
|
||||
.addressRepo(repo)
|
||||
.listener { }
|
||||
.build()
|
||||
importer = WifImporter(ctx, javaClass.classLoader.getResourceAsStream("nuked.dat"))
|
||||
assertEquals(81, importer.getIdentities().size)
|
||||
exporter = WifExporter(ctx)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure all identities in context are added`() {
|
||||
whenever(repo.getIdentities()).thenReturn(importer.getIdentities())
|
||||
exporter.addAll()
|
||||
val result = exporter.toString()
|
||||
var count = 0
|
||||
for (i in 0..result.length - 1) {
|
||||
if (result[i] == '[') count++
|
||||
}
|
||||
assertEquals(importer.getIdentities().size, count)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure all from a collection are added`() {
|
||||
exporter.addAll(importer.getIdentities())
|
||||
val result = exporter.toString()
|
||||
var count = 0
|
||||
for (i in 0..result.length - 1) {
|
||||
if (result[i] == '[') count++
|
||||
}
|
||||
assertEquals(importer.getIdentities().size, count)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure identity is added`() {
|
||||
val expected = "[BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn]" + System.lineSeparator() +
|
||||
"label = Nuked Address" + System.lineSeparator() +
|
||||
"enabled = true" + System.lineSeparator() +
|
||||
"decoy = false" + System.lineSeparator() +
|
||||
"noncetrialsperbyte = 320" + System.lineSeparator() +
|
||||
"payloadlengthextrabytes = 14000" + System.lineSeparator() +
|
||||
"privsigningkey = 5KU2gbe9u4rKJ8PHYb1rvwMnZnAJj4gtV5GLwoYckeYzygWUzB9" + System.lineSeparator() +
|
||||
"privencryptionkey = 5KHd4c6cavd8xv4kzo3PwnVaYuBgEfg7voPQ5V97aZKgpYBXGck" + System.lineSeparator() +
|
||||
System.lineSeparator()
|
||||
importer = WifImporter(ctx, expected)
|
||||
exporter.addIdentity(importer.getIdentities()[0])
|
||||
assertEquals(expected, exporter.toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure chan is added`() {
|
||||
val expected = "[BM-2cW67GEKkHGonXKZLCzouLLxnLym3azS8r]" + System.lineSeparator() +
|
||||
"label = general" + System.lineSeparator() +
|
||||
"enabled = true" + System.lineSeparator() +
|
||||
"decoy = false" + System.lineSeparator() +
|
||||
"chan = true" + System.lineSeparator() +
|
||||
"noncetrialsperbyte = 1000" + System.lineSeparator() +
|
||||
"payloadlengthextrabytes = 1000" + System.lineSeparator() +
|
||||
"privsigningkey = 5Jnbdwc4u4DG9ipJxYLznXSvemkRFueQJNHujAQamtDDoX3N1eQ" + System.lineSeparator() +
|
||||
"privencryptionkey = 5JrDcFtQDv5ydcHRW6dfGUEvThoxCCLNEUaxQfy8LXXgTJzVAcq" + System.lineSeparator() +
|
||||
System.lineSeparator()
|
||||
val chan = ctx.joinChan("general", "BM-2cW67GEKkHGonXKZLCzouLLxnLym3azS8r")
|
||||
exporter.addIdentity(chan)
|
||||
assertEquals(expected, exporter.toString())
|
||||
}
|
||||
}
|
@ -1,116 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.wif;
|
||||
|
||||
import ch.dissem.bitmessage.BitmessageContext;
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.ports.*;
|
||||
import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class WifImporterTest {
|
||||
private AddressRepository repo = mock(AddressRepository.class);
|
||||
private BitmessageContext ctx;
|
||||
private WifImporter importer;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
ctx = new BitmessageContext.Builder()
|
||||
.cryptography(BouncyCryptography.INSTANCE)
|
||||
.networkHandler(mock(NetworkHandler.class))
|
||||
.inventory(mock(Inventory.class))
|
||||
.messageRepo(mock(MessageRepository.class))
|
||||
.powRepo(mock(ProofOfWorkRepository.class))
|
||||
.nodeRegistry(mock(NodeRegistry.class))
|
||||
.addressRepo(repo)
|
||||
.build();
|
||||
importer = new WifImporter(ctx, getClass().getClassLoader().getResourceAsStream("nuked.dat"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testImportSingleIdentity() throws Exception {
|
||||
importer = new WifImporter(ctx, "[BM-2cWJ4UFRTCehWuWNsW8fJkAYMxU4S8jxci]\n" +
|
||||
"label = Nuked Address\n" +
|
||||
"enabled = true\n" +
|
||||
"decoy = false\n" +
|
||||
"noncetrialsperbyte = 320\n" +
|
||||
"payloadlengthextrabytes = 14000\n" +
|
||||
"privsigningkey = 5JU5t2JA58sP5aJwKAcrYg5EpBA9bJPrBSaFfaZ7ogmwTMDCfHL\n" +
|
||||
"privencryptionkey = 5Kkx5MwjQcM4kyduKvCEPM6nVNynMdRcg88VQ5iVDWUekMz1igH");
|
||||
assertEquals(1, importer.getIdentities().size());
|
||||
BitmessageAddress identity = importer.getIdentities().get(0);
|
||||
assertEquals("BM-2cWJ4UFRTCehWuWNsW8fJkAYMxU4S8jxci", identity.getAddress());
|
||||
assertEquals("Nuked Address", identity.getAlias());
|
||||
assertEquals(320, identity.getPubkey().getNonceTrialsPerByte());
|
||||
assertEquals(14000, identity.getPubkey().getExtraBytes());
|
||||
assertNotNull("Private key", identity.getPrivateKey());
|
||||
assertEquals(32, identity.getPrivateKey().getPrivateEncryptionKey().length);
|
||||
assertEquals(32, identity.getPrivateKey().getPrivateSigningKey().length);
|
||||
assertFalse(identity.isChan());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetIdentities() throws Exception {
|
||||
List<BitmessageAddress> identities = importer.getIdentities();
|
||||
assertEquals(81, identities.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportAll() throws Exception {
|
||||
importer.importAll();
|
||||
verify(repo, times(81)).save(any(BitmessageAddress.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportAllFromCollection() throws Exception {
|
||||
List<BitmessageAddress> identities = importer.getIdentities();
|
||||
importer.importAll(identities);
|
||||
for (BitmessageAddress identity : identities) {
|
||||
verify(repo, times(1)).save(identity);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportIdentity() throws Exception {
|
||||
List<BitmessageAddress> identities = importer.getIdentities();
|
||||
importer.importIdentity(identities.get(0));
|
||||
verify(repo, times(1)).save(identities.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ensureChanIsImported() throws Exception {
|
||||
importer = new WifImporter(ctx, "[BM-2cW67GEKkHGonXKZLCzouLLxnLym3azS8r]\n" +
|
||||
"label = [chan] general\n" +
|
||||
"enabled = true\n" +
|
||||
"decoy = false\n" +
|
||||
"chan = true\n" +
|
||||
"noncetrialsperbyte = 1000\n" +
|
||||
"payloadlengthextrabytes = 1000\n" +
|
||||
"privsigningkey = 5Jnbdwc4u4DG9ipJxYLznXSvemkRFueQJNHujAQamtDDoX3N1eQ\n" +
|
||||
"privencryptionkey = 5JrDcFtQDv5ydcHRW6dfGUEvThoxCCLNEUaxQfy8LXXgTJzVAcq\n");
|
||||
assertEquals(1, importer.getIdentities().size());
|
||||
BitmessageAddress chan = importer.getIdentities().get(0);
|
||||
assertTrue(chan.isChan());
|
||||
}
|
||||
}
|
132
wif/src/test/java/ch/dissem/bitmessage/wif/WifImporterTest.kt
Normal file
132
wif/src/test/java/ch/dissem/bitmessage/wif/WifImporterTest.kt
Normal file
@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.wif
|
||||
|
||||
import ch.dissem.bitmessage.BitmessageContext
|
||||
import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography
|
||||
import ch.dissem.bitmessage.ports.AddressRepository
|
||||
import com.nhaarman.mockito_kotlin.any
|
||||
import com.nhaarman.mockito_kotlin.mock
|
||||
import com.nhaarman.mockito_kotlin.times
|
||||
import com.nhaarman.mockito_kotlin.verify
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class WifImporterTest {
|
||||
private val repo = mock<AddressRepository>()
|
||||
private lateinit var ctx: BitmessageContext
|
||||
private lateinit var importer: WifImporter
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
ctx = BitmessageContext.Builder()
|
||||
.cryptography(BouncyCryptography())
|
||||
.networkHandler(mock())
|
||||
.inventory(mock())
|
||||
.messageRepo(mock())
|
||||
.powRepo(mock())
|
||||
.nodeRegistry(mock())
|
||||
.addressRepo(repo)
|
||||
.listener { }
|
||||
.build()
|
||||
importer = WifImporter(ctx, javaClass.classLoader.getResourceAsStream("nuked.dat"))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `ensure single identity is imported`() {
|
||||
importer = WifImporter(ctx, "[BM-2cWJ4UFRTCehWuWNsW8fJkAYMxU4S8jxci]\n" +
|
||||
"label = Nuked Address\n" +
|
||||
"enabled = true\n" +
|
||||
"decoy = false\n" +
|
||||
"noncetrialsperbyte = 320\n" +
|
||||
"payloadlengthextrabytes = 14000\n" +
|
||||
"privsigningkey = 5JU5t2JA58sP5aJwKAcrYg5EpBA9bJPrBSaFfaZ7ogmwTMDCfHL\n" +
|
||||
"privencryptionkey = 5Kkx5MwjQcM4kyduKvCEPM6nVNynMdRcg88VQ5iVDWUekMz1igH")
|
||||
assertEquals(1, importer.getIdentities().size)
|
||||
val identity = importer.getIdentities()[0]
|
||||
assertEquals("BM-2cWJ4UFRTCehWuWNsW8fJkAYMxU4S8jxci", identity.address)
|
||||
assertEquals("Nuked Address", identity.alias)
|
||||
assertEquals(320L, identity.pubkey?.nonceTrialsPerByte)
|
||||
assertEquals(14000L, identity.pubkey?.extraBytes)
|
||||
assertNotNull("Private key", identity.privateKey)
|
||||
assertEquals(32, identity.privateKey?.privateEncryptionKey?.size)
|
||||
assertEquals(32, identity.privateKey?.privateSigningKey?.size)
|
||||
assertFalse(identity.isChan)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure all identities are retrieved`() {
|
||||
val identities = importer.getIdentities()
|
||||
assertEquals(81, identities.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure all identities are imported`() {
|
||||
importer.importAll()
|
||||
verify(repo, times(81)).save(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure all identities in collection are imported`() {
|
||||
val identities = importer.getIdentities()
|
||||
importer.importAll(identities)
|
||||
for (identity in identities) {
|
||||
verify(repo, times(1)).save(identity)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure single identity from list is imported`() {
|
||||
val identities = importer.getIdentities()
|
||||
importer.importIdentity(identities[0])
|
||||
verify(repo, times(1)).save(identities[0])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure chan is imported`() {
|
||||
importer = WifImporter(ctx, "[BM-2cW67GEKkHGonXKZLCzouLLxnLym3azS8r]\n" +
|
||||
"label = [chan] general\n" +
|
||||
"enabled = true\n" +
|
||||
"decoy = false\n" +
|
||||
"chan = true\n" +
|
||||
"noncetrialsperbyte = 1000\n" +
|
||||
"payloadlengthextrabytes = 1000\n" +
|
||||
"privsigningkey = 5Jnbdwc4u4DG9ipJxYLznXSvemkRFueQJNHujAQamtDDoX3N1eQ\n" +
|
||||
"privencryptionkey = 5JrDcFtQDv5ydcHRW6dfGUEvThoxCCLNEUaxQfy8LXXgTJzVAcq\n")
|
||||
assertEquals(1, importer.getIdentities().size)
|
||||
val chan = importer.getIdentities()[0]
|
||||
assertTrue(chan.isChan)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user