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