23 Commits

Author SHA1 Message Date
519f457476 🐛 Fix connectivity issues and improve code 2018-10-15 19:57:01 +02:00
fe9fa0ba2f 🐘 Add settings to reduce memory usage
* NioNetworkHandler lets you tweak the minimum number of connections
* BufferPool can now set a limit to how many Buffers it retains.
  This one might still need some tweaking.
2018-06-12 07:00:52 +02:00
37cda3df56 🔥 Remove duplicate code 2018-05-31 22:33:26 +02:00
fafabf64a3 🔥 Remove duplicate code 2018-05-31 16:58:29 +02:00
7b9694e660 😴 Improve code quality 2018-05-31 07:32:07 +02:00
ce86ab55c3 ⬆ JUnit version bump 2018-05-30 22:08:53 +02:00
25e118b88e 🔥 Remove unnecessary methods 2018-05-30 17:01:05 +02:00
cbebc38579 Improve code quality 2018-05-29 21:00:25 +02:00
b44a2f8809 🚀 Improve performance 2018-05-25 20:47:40 +02:00
c7c285a2c1 Improve conversation class
* make it serializable
* provide either first unread or last message as extract
2018-03-12 19:41:56 +01:00
81fc50ec37 Improvements for working with conversations 2018-03-05 10:12:21 +01:00
f1403bcd00 Bumped Gradle wrapper and Kotlin dependency 2018-03-05 09:50:24 +01:00
e9acb0071e Make network handler more robust 2018-02-21 07:44:53 +01:00
c425298b67 Use JUnit 5 for tests, bump dependencies 2018-02-20 14:04:40 +01:00
681ea148db Allow missing recipient for drafts 2018-02-16 17:04:24 +01:00
fab1c06135 Refactor to utilize Kotlin features 2018-02-16 17:04:08 +01:00
b93f382ccd Bump Kotlin to version 1.2.21 2018-02-16 17:03:37 +01:00
00e4461043 Fix issues with Java 7 compatibility 2018-02-16 16:57:08 +01:00
18f870a4cc Switched from commons-lang3 to commons-text 2017-11-27 21:46:19 +01:00
278d5b05e6 Split LabelRepository off the MessageRepository 2017-11-26 20:30:05 +01:00
ddb2073c2f Refactored BitmessageContext creation 2017-11-25 20:34:11 +01:00
a5c78fd8cf Renamed InputStreams from in and is to input, so it doesn't look strange in kotlin 2017-11-24 07:37:04 +01:00
8cbdce6eac Refactored to use StreamableWriter
Bumped the msgpack library to 2.0.1 (the 2.0.0 build was fubar)
2017-11-21 10:44:41 +01:00
120 changed files with 2848 additions and 2296 deletions

2
.gitignore vendored
View File

@ -49,7 +49,7 @@ gradle-app.setting
## Plugin-specific files: ## Plugin-specific files:
# IntelliJ # IntelliJ
/out/ out/
# mpeltonen/sbt-idea plugin # mpeltonen/sbt-idea plugin
.idea_modules/ .idea_modules/

View File

@ -1,5 +1,5 @@
buildscript { buildscript {
ext.kotlin_version = '1.1.4-3' ext.kotlin_version = '1.2.71'
repositories { repositories {
mavenCentral() mavenCentral()
} }
@ -8,17 +8,17 @@ buildscript {
} }
} }
plugins { plugins {
id 'com.github.ben-manes.versions' version '0.15.0' id 'com.github.ben-manes.versions' version '0.17.0'
id "io.spring.dependency-management" version "1.0.3.RELEASE" id "io.spring.dependency-management" version "1.0.4.RELEASE"
} }
subprojects { subprojects {
apply plugin: 'io.spring.dependency-management'
apply plugin: 'kotlin' apply plugin: 'kotlin'
apply plugin: 'maven' apply plugin: 'maven'
apply plugin: 'signing' apply plugin: 'signing'
apply plugin: 'jacoco' apply plugin: 'jacoco'
apply plugin: 'gitflow-version' apply plugin: 'gitflow-version'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'com.github.ben-manes.versions' apply plugin: 'com.github.ben-manes.versions'
sourceCompatibility = 1.7 sourceCompatibility = 1.7
@ -31,8 +31,8 @@ subprojects {
jcenter() jcenter()
} }
dependencies { dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7"
compile "org.jetbrains.kotlin:kotlin-reflect" implementation "org.jetbrains.kotlin:kotlin-reflect"
} }
test { test {
@ -130,7 +130,7 @@ subprojects {
dependencyManagement { dependencyManagement {
dependencies { dependencies {
dependencySet(group: 'org.jetbrains.kotlin', version: "$kotlin_version") { dependencySet(group: 'org.jetbrains.kotlin', version: "$kotlin_version") {
entry 'kotlin-stdlib-jre7' entry 'kotlin-stdlib-jdk7'
entry 'kotlin-reflect' entry 'kotlin-reflect'
} }
dependencySet(group: 'org.slf4j', version: '1.7.25') { dependencySet(group: 'org.slf4j', version: '1.7.25') {
@ -138,20 +138,22 @@ subprojects {
entry 'slf4j-simple' entry 'slf4j-simple'
} }
dependency 'ch.dissem.msgpack:msgpack:2.0.0' dependency 'ch.dissem.msgpack:msgpack:2.0.1'
dependency 'org.bouncycastle:bcprov-jdk15on:1.57' dependency 'org.bouncycastle:bcprov-jdk15on:1.60'
dependency 'com.madgag.spongycastle:prov:1.56.0.0' dependency 'com.madgag.spongycastle:prov:1.58.0.0'
dependency 'org.apache.commons:commons-lang3:3.6' dependency 'org.apache.commons:commons-text:1.5'
dependency 'org.flywaydb:flyway-core:4.2.0' dependency 'org.flywaydb:flyway-core:5.2.0'
dependency 'com.beust:klaxon:0.31' dependency 'com.beust:klaxon:3.0.8'
dependency 'args4j:args4j:2.33' dependency 'args4j:args4j:2.33'
dependency 'org.ini4j:ini4j:0.5.4' dependency 'org.ini4j:ini4j:0.5.4'
dependency 'com.h2database:h2:1.4.196' dependency 'com.h2database:h2:1.4.197'
dependency 'junit:junit:4.12' dependency 'org.hamcrest:java-hamcrest:2.0.0.0'
dependency 'org.hamcrest:hamcrest-library:1.3' dependency 'com.nhaarman:mockito-kotlin:1.6.0'
dependency 'com.nhaarman:mockito-kotlin:1.5.0'
dependency 'org.junit.jupiter:junit-jupiter-api:5.3.1'
dependency 'org.junit.jupiter:junit-jupiter-engine:5.3.1'
} }
} }
} }

View File

@ -25,10 +25,10 @@ artifacts {
dependencies { dependencies {
compile 'org.slf4j:slf4j-api' compile 'org.slf4j:slf4j-api'
compile 'ch.dissem.msgpack:msgpack:1.0.0' compile 'ch.dissem.msgpack:msgpack'
testCompile 'junit:junit:4.12' testCompile 'com.nhaarman:mockito-kotlin'
testCompile 'org.hamcrest:hamcrest-library:1.3' testCompile 'org.junit.jupiter:junit-jupiter-api'
testCompile 'com.nhaarman:mockito-kotlin:1.5.0' testRuntime 'org.junit.jupiter:junit-jupiter-engine'
testCompile project(':cryptography-bc') testCompile project(':cryptography-bc')
} }

View File

@ -16,6 +16,7 @@
package ch.dissem.bitmessage package ch.dissem.bitmessage
import ch.dissem.bitmessage.BitmessageContext.Companion.version
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE
import ch.dissem.bitmessage.entity.BitmessageAddress import ch.dissem.bitmessage.entity.BitmessageAddress
@ -33,7 +34,6 @@ import ch.dissem.bitmessage.exception.DecryptionFailedException
import ch.dissem.bitmessage.factory.Factory import ch.dissem.bitmessage.factory.Factory
import ch.dissem.bitmessage.ports.* import ch.dissem.bitmessage.ports.*
import ch.dissem.bitmessage.utils.Property import ch.dissem.bitmessage.utils.Property
import ch.dissem.bitmessage.utils.UnixTime.HOUR
import ch.dissem.bitmessage.utils.UnixTime.MINUTE import ch.dissem.bitmessage.utils.UnixTime.MINUTE
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.net.InetAddress import java.net.InetAddress
@ -58,57 +58,7 @@ import kotlin.properties.Delegates
* *
* The port defaults to 8444 (the default Bitmessage port) * The port defaults to 8444 (the default Bitmessage port)
*/ */
class BitmessageContext( class BitmessageContext private constructor(builder: BitmessageContext.Builder) {
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(),
userAgent: String? = null,
port: Int = 8444,
connectionTTL: Long = 30 * MINUTE,
connectionLimit: Int = 150,
sendPubkeyOnIdentityCreation: Boolean = true,
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.userAgent,
builder.port,
builder.connectionTTL,
builder.connectionLimit,
builder.sendPubkeyOnIdentityCreation,
builder.doMissingProofOfWorkDelay
)
private val sendPubkeyOnIdentityCreation: Boolean
/** /**
* The [InternalContext] - normally you wouldn't need it, * The [InternalContext] - normally you wouldn't need it,
@ -123,6 +73,9 @@ class BitmessageContext(
val addresses: AddressRepository val addresses: AddressRepository
@JvmName("addresses") get @JvmName("addresses") get
val labels: LabelRepository
@JvmName("labels") get
val messages: MessageRepository val messages: MessageRepository
@JvmName("messages") get @JvmName("messages") get
@ -135,7 +88,7 @@ class BitmessageContext(
*features *features
)) ))
internals.addressRepository.save(identity) internals.addressRepository.save(identity)
if (sendPubkeyOnIdentityCreation) { if (internals.preferences.sendPubkeyOnIdentityCreation) {
internals.sendPubkey(identity, identity.stream) internals.sendPubkey(identity, identity.stream)
} }
return identity return identity
@ -262,9 +215,8 @@ class BitmessageContext(
* @param request the request * @param request the request
* @return the response * @return the response
*/ */
fun send(server: InetAddress, port: Int, request: CustomMessage): CustomMessage { fun send(server: InetAddress, port: Int, request: CustomMessage): CustomMessage =
return internals.networkHandler.send(server, port, request) internals.networkHandler.send(server, port, request)
}
/** /**
* Removes expired objects from the inventory. You should call this method regularly, * Removes expired objects from the inventory. You should call this method regularly,
@ -272,6 +224,7 @@ class BitmessageContext(
*/ */
fun cleanup() { fun cleanup() {
internals.inventory.cleanup() internals.inventory.cleanup()
internals.nodeRegistry.cleanup()
} }
/** /**
@ -326,7 +279,7 @@ class BitmessageContext(
fun status(): Property { fun status(): Property {
return Property("status", return Property("status",
Property("user agent", internals.userAgent), Property("user agent", internals.preferences.userAgent),
internals.networkHandler.getNetworkStatus(), internals.networkHandler.getNetworkStatus(),
Property("unacknowledged", internals.messageRepository.findMessagesToResend().size) Property("unacknowledged", internals.messageRepository.findMessagesToResend().size)
) )
@ -343,29 +296,23 @@ class BitmessageContext(
} }
} }
/**
* Kotlin users: you might want to use [BitmessageContext.build] instead.
*/
class Builder { class Builder {
internal var port = 8444 var inventory by Delegates.notNull<Inventory>()
internal var inventory by Delegates.notNull<Inventory>() var nodeRegistry by Delegates.notNull<NodeRegistry>()
internal var nodeRegistry by Delegates.notNull<NodeRegistry>() var networkHandler by Delegates.notNull<NetworkHandler>()
internal var networkHandler by Delegates.notNull<NetworkHandler>() var addressRepo by Delegates.notNull<AddressRepository>()
internal var addressRepo by Delegates.notNull<AddressRepository>() var labelRepo by Delegates.notNull<LabelRepository>()
internal var messageRepo by Delegates.notNull<MessageRepository>() var messageRepo by Delegates.notNull<MessageRepository>()
internal var proofOfWorkRepository by Delegates.notNull<ProofOfWorkRepository>() var proofOfWorkRepo by Delegates.notNull<ProofOfWorkRepository>()
internal var proofOfWorkEngine: ProofOfWorkEngine? = null var proofOfWorkEngine: ProofOfWorkEngine? = null
internal var cryptography by Delegates.notNull<Cryptography>() var cryptography by Delegates.notNull<Cryptography>()
internal var customCommandHandler: CustomCommandHandler? = null var customCommandHandler: CustomCommandHandler? = null
internal var labeler: Labeler? = null var labeler: Labeler? = null
internal var userAgent: String? = null var listener by Delegates.notNull<Listener>()
internal var listener by Delegates.notNull<Listener>() val preferences = Preferences()
internal var connectionLimit = 150
internal var connectionTTL = 30 * MINUTE
internal var sendPubkeyOnIdentityCreation = true
internal var doMissingProofOfWorkDelay = 30
fun port(port: Int): Builder {
this.port = port
return this
}
fun inventory(inventory: Inventory): Builder { fun inventory(inventory: Inventory): Builder {
this.inventory = inventory this.inventory = inventory
@ -387,13 +334,18 @@ class BitmessageContext(
return this return this
} }
fun labelRepo(labelRepo: LabelRepository): Builder {
this.labelRepo = labelRepo
return this
}
fun messageRepo(messageRepo: MessageRepository): Builder { fun messageRepo(messageRepo: MessageRepository): Builder {
this.messageRepo = messageRepo this.messageRepo = messageRepo
return this return this
} }
fun powRepo(proofOfWorkRepository: ProofOfWorkRepository): Builder { fun powRepo(proofOfWorkRepository: ProofOfWorkRepository): Builder {
this.proofOfWorkRepository = proofOfWorkRepository this.proofOfWorkRepo = proofOfWorkRepository
return this return this
} }
@ -422,7 +374,7 @@ class BitmessageContext(
return this return this
} }
@JvmName("kotlinListener") @JvmSynthetic
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) {
@ -432,63 +384,41 @@ class BitmessageContext(
return this return this
} }
fun connectionLimit(connectionLimit: Int): Builder { fun build() = BitmessageContext(this)
this.connectionLimit = connectionLimit
return this
} }
fun connectionTTL(hours: Int): Builder {
this.connectionTTL = hours * HOUR
return this
}
fun doMissingProofOfWorkDelay(seconds: Int) {
this.doMissingProofOfWorkDelay = seconds
}
/**
* By default a client will send the public key when an identity is being created. On weaker devices
* this behaviour might not be desirable.
*/
fun doNotSendPubkeyOnIdentityCreation(): Builder {
this.sendPubkeyOnIdentityCreation = false
return this
}
fun build(): BitmessageContext {
return BitmessageContext(this)
}
}
init { init {
this.labeler = labeler this.labeler = builder.labeler ?: DefaultLabeler()
this.internals = InternalContext( this.internals = InternalContext(
cryptography, builder.cryptography,
inventory, builder.inventory,
nodeRegistry, builder.nodeRegistry,
networkHandler, builder.networkHandler,
addressRepository, builder.addressRepo,
messageRepository, builder.labelRepo,
proofOfWorkRepository, builder.messageRepo,
proofOfWorkEngine, builder.proofOfWorkRepo,
customCommandHandler, builder.proofOfWorkEngine ?: MultiThreadedPOWEngine(),
listener, 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,
labeler, labeler,
userAgent?.let { "/$it/Jabit:$version/" } ?: "/Jabit:$version/", builder.preferences
port,
connectionTTL,
connectionLimit
) )
this.addresses = addressRepository this.addresses = builder.addressRepo
this.messages = messageRepository this.labels = builder.labelRepo
this.sendPubkeyOnIdentityCreation = sendPubkeyOnIdentityCreation this.messages = builder.messageRepo
(listener as? Listener.WithContext)?.setContext(this) (builder.listener as? Listener.WithContext)?.setContext(this)
internals.proofOfWorkService.doMissingProofOfWork(doMissingProofOfWorkDelayInSeconds * 1000L) internals.proofOfWorkService.doMissingProofOfWork(builder.preferences.doMissingProofOfWorkDelayInSeconds * 1000L)
} }
companion object { companion object {
@JvmField val CURRENT_VERSION = 3 @JvmField
val CURRENT_VERSION = 3
private val LOG = LoggerFactory.getLogger(BitmessageContext::class.java) private val LOG = LoggerFactory.getLogger(BitmessageContext::class.java)
val version: String by lazy { val version: String by lazy {
@ -496,5 +426,40 @@ class BitmessageContext(
} }
@JvmStatic get @JvmStatic get
@JvmSynthetic
inline fun build(block: Builder.() -> Unit): BitmessageContext {
val builder = Builder()
block(builder)
return builder.build()
}
} }
} }
class Preferences {
var port = 8444
/**
* Defaults to "/Jabit:<version>/", and whatever you set will be inserted into "/<your user agent>/Jabit:<version>/"
*/
var userAgent = "/Jabit:$version/"
set(value) {
field = "/$value/Jabit:$version/"
}
/**
* Time to live for any connection
*/
var connectionTTL = 30 * MINUTE
/**
* Maximum number of connections. Values below 8 would probably result in erratic behaviour, so you shouldn't do that.
*/
var connectionLimit = 150
/**
* By default a client will send the public key when an identity is being created. On weaker devices
* this behaviour might not be desirable.
*/
var sendPubkeyOnIdentityCreation = true
/**
* Delay in seconds before outstandinng proof of work is calculated.
*/
var doMissingProofOfWorkDelayInSeconds = 30
}

View File

@ -40,22 +40,19 @@ import java.util.concurrent.Executors
*/ */
class InternalContext( class InternalContext(
val cryptography: Cryptography, val cryptography: Cryptography,
val inventory: ch.dissem.bitmessage.ports.Inventory, val inventory: Inventory,
val nodeRegistry: NodeRegistry, val nodeRegistry: NodeRegistry,
val networkHandler: NetworkHandler, val networkHandler: NetworkHandler,
val addressRepository: AddressRepository, val addressRepository: AddressRepository,
val messageRepository: ch.dissem.bitmessage.ports.MessageRepository, val labelRepository: LabelRepository,
val messageRepository: MessageRepository,
val proofOfWorkRepository: ProofOfWorkRepository, val proofOfWorkRepository: ProofOfWorkRepository,
val proofOfWorkEngine: ProofOfWorkEngine, val proofOfWorkEngine: ProofOfWorkEngine,
val customCommandHandler: CustomCommandHandler, val customCommandHandler: CustomCommandHandler,
listener: BitmessageContext.Listener, listener: BitmessageContext.Listener,
val labeler: Labeler, val labeler: Labeler,
val userAgent: String, val preferences: Preferences
val port: Int,
val connectionTTL: Long,
val connectionLimit: Int
) { ) {
private val threadPool = Executors.newCachedThreadPool() private val threadPool = Executors.newCachedThreadPool()
@ -220,7 +217,9 @@ class 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
@JvmField val NETWORK_EXTRA_BYTES: Long = 1000 val NETWORK_NONCE_TRIALS_PER_BYTE: Long = 1000
@JvmField
val NETWORK_EXTRA_BYTES: Long = 1000
} }
} }

View File

@ -25,19 +25,28 @@ import java.nio.ByteBuffer
* The 'addr' command holds a list of known active Bitmessage nodes. * The 'addr' command holds a list of known active Bitmessage nodes.
*/ */
data class Addr constructor(val addresses: List<NetworkAddress>) : MessagePayload { data class Addr constructor(val addresses: List<NetworkAddress>) : MessagePayload {
override val command: MessagePayload.Command = MessagePayload.Command.ADDR override val command: MessagePayload.Command = MessagePayload.Command.ADDR
override fun writer(): StreamableWriter = Writer(this)
private class Writer(
private val item: Addr
) : StreamableWriter {
override fun write(out: OutputStream) { override fun write(out: OutputStream) {
Encode.varInt(addresses.size, out) Encode.varInt(item.addresses.size, out)
for (address in addresses) { for (address in item.addresses) {
address.write(out) address.writer().write(out)
} }
} }
override fun write(buffer: ByteBuffer) { override fun write(buffer: ByteBuffer) {
Encode.varInt(addresses.size, buffer) Encode.varInt(item.addresses.size, buffer)
for (address in addresses) { for (address in item.addresses) {
address.write(buffer) address.writer().write(buffer)
} }
} }
}
} }

View File

@ -116,15 +116,15 @@ class BitmessageAddress : Serializable {
constructor(address: String) { constructor(address: String) {
this.address = address this.address = address
val bytes = Base58.decode(address.substring(3)) val bytes = Base58.decode(address.substring(3))
val `in` = ByteArrayInputStream(bytes) val input = ByteArrayInputStream(bytes)
val counter = AccessCounter() val counter = AccessCounter()
this.version = varInt(`in`, counter) this.version = varInt(input, counter)
this.stream = varInt(`in`, counter) this.stream = varInt(input, counter)
this.ripe = Bytes.expand(bytes(`in`, bytes.size - counter.length() - 4), 20) this.ripe = Bytes.expand(bytes(input, bytes.size - counter.length() - 4), 20)
// test checksum // test checksum
var checksum = cryptography().doubleSha512(bytes, bytes.size - 4) var checksum = cryptography().doubleSha512(bytes, bytes.size - 4)
val expectedChecksum = bytes(`in`, 4) val expectedChecksum = bytes(input, 4)
for (i in 0..3) { for (i in 0..3) {
if (expectedChecksum[i] != checksum[i]) if (expectedChecksum[i] != checksum[i])
throw IllegalArgumentException("Checksum of address failed") throw IllegalArgumentException("Checksum of address failed")

View File

@ -33,22 +33,26 @@ open class CustomMessage(val customCommand: String, private val data: ByteArray?
override val command: MessagePayload.Command = MessagePayload.Command.CUSTOM override val command: MessagePayload.Command = MessagePayload.Command.CUSTOM
val isError: Boolean val isError = COMMAND_ERROR == customCommand
fun getData(): ByteArray { fun getData(): ByteArray {
if (data != null) { return data ?: {
return data
} else {
val out = ByteArrayOutputStream() val out = ByteArrayOutputStream()
write(out) writer().write(out)
return out.toByteArray() out.toByteArray()
} }.invoke()
} }
override fun writer(): StreamableWriter = Writer(this)
protected open class Writer(
private val item: CustomMessage
) : StreamableWriter {
override fun write(out: OutputStream) { override fun write(out: OutputStream) {
if (data != null) { if (item.data != null) {
Encode.varString(customCommand, out) Encode.varString(item.customCommand, out)
out.write(data) out.write(item.data)
} else { } else {
throw ApplicationException("Tried to write custom message without data. " throw ApplicationException("Tried to write custom message without data. "
+ "Programmer: did you forget to override #write()?") + "Programmer: did you forget to override #write()?")
@ -56,31 +60,27 @@ open class CustomMessage(val customCommand: String, private val data: ByteArray?
} }
override fun write(buffer: ByteBuffer) { override fun write(buffer: ByteBuffer) {
if (data != null) { if (item.data != null) {
Encode.varString(customCommand, buffer) Encode.varString(item.customCommand, buffer)
buffer.put(data) buffer.put(item.data)
} else { } else {
throw ApplicationException("Tried to write custom message without data. " throw ApplicationException("Tried to write custom message without data. "
+ "Programmer: did you forget to override #write()?") + "Programmer: did you forget to override #write()?")
} }
} }
}
companion object { companion object {
val COMMAND_ERROR = "ERROR" val COMMAND_ERROR = "ERROR"
@JvmStatic @JvmStatic
fun read(`in`: InputStream, length: Int): CustomMessage { fun read(input: InputStream, length: Int): CustomMessage {
val counter = AccessCounter() val counter = AccessCounter()
return CustomMessage(varString(`in`, counter), bytes(`in`, length - counter.length())) return CustomMessage(varString(input, counter), bytes(input, length - counter.length()))
} }
@JvmStatic @JvmStatic
fun error(message: String): CustomMessage { fun error(message: String) = CustomMessage(COMMAND_ERROR, message.toByteArray(charset("UTF-8")))
return CustomMessage(COMMAND_ERROR, message.toByteArray(charset("UTF-8")))
}
}
init {
this.isError = COMMAND_ERROR == customCommand
} }
} }

View File

@ -17,9 +17,6 @@
package ch.dissem.bitmessage.entity package ch.dissem.bitmessage.entity
import ch.dissem.bitmessage.entity.valueobject.InventoryVector import ch.dissem.bitmessage.entity.valueobject.InventoryVector
import ch.dissem.bitmessage.utils.Encode
import java.io.OutputStream
import java.nio.ByteBuffer
/** /**
* The 'getdata' command is used to request objects from a node. * The 'getdata' command is used to request objects from a node.
@ -28,21 +25,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 writer(): StreamableWriter = Writer(this)
Encode.varInt(inventory.size, out)
for (iv in inventory) {
iv.write(out)
}
}
override fun write(buffer: ByteBuffer) { private class Writer(
Encode.varInt(inventory.size, buffer) item: GetData
for (iv in inventory) { ) : InventoryWriter(item.inventory)
iv.write(buffer)
}
}
companion object { companion object {
@JvmField val MAX_INVENTORY_SIZE = 50000 @JvmField
val MAX_INVENTORY_SIZE = 50000
} }
} }

View File

@ -17,9 +17,6 @@
package ch.dissem.bitmessage.entity package ch.dissem.bitmessage.entity
import ch.dissem.bitmessage.entity.valueobject.InventoryVector import ch.dissem.bitmessage.entity.valueobject.InventoryVector
import ch.dissem.bitmessage.utils.Encode
import java.io.OutputStream
import java.nio.ByteBuffer
/** /**
* The 'inv' command holds up to 50000 inventory vectors, i.e. hashes of inventory items. * The 'inv' command holds up to 50000 inventory vectors, i.e. hashes of inventory items.
@ -28,17 +25,9 @@ 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 writer(): StreamableWriter = Writer(this)
Encode.varInt(inventory.size, out)
for (iv in inventory) {
iv.write(out)
}
}
override fun write(buffer: ByteBuffer) { private class Writer(
Encode.varInt(inventory.size, buffer) item: Inv
for (iv in inventory) { ) : InventoryWriter(item.inventory)
iv.write(buffer)
}
}
} }

View File

@ -0,0 +1,40 @@
/*
* Copyright 2018 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.entity
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
import ch.dissem.bitmessage.utils.Encode
import java.io.OutputStream
import java.nio.ByteBuffer
internal open class InventoryWriter(private val inventory: List<InventoryVector>) : StreamableWriter {
override fun write(out: OutputStream) {
Encode.varInt(inventory.size, out)
for (iv in inventory) {
iv.writer().write(out)
}
}
override fun write(buffer: ByteBuffer) {
Encode.varInt(inventory.size, buffer)
for (iv in inventory) {
iv.writer().write(buffer)
}
}
}

View File

@ -18,7 +18,6 @@ package ch.dissem.bitmessage.entity
import ch.dissem.bitmessage.utils.Encode import ch.dissem.bitmessage.utils.Encode
import ch.dissem.bitmessage.utils.Singleton.cryptography import ch.dissem.bitmessage.utils.Singleton.cryptography
import java.io.IOException
import java.io.OutputStream import java.io.OutputStream
import java.nio.ByteBuffer import java.nio.ByteBuffer
@ -32,26 +31,24 @@ data class NetworkMessage(
val payload: MessagePayload val payload: MessagePayload
) : Streamable { ) : Streamable {
/** override fun writer(): Writer = Writer(this)
* First 4 bytes of sha512(payload)
*/ class Writer internal constructor(
private fun getChecksum(bytes: ByteArray): ByteArray { private val item: NetworkMessage
val d = cryptography().sha512(bytes) ) : StreamableWriter {
return byteArrayOf(d[0], d[1], d[2], d[3])
}
override fun write(out: OutputStream) { override fun write(out: OutputStream) {
// magic // magic
Encode.int32(MAGIC, 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 = item.payload.command.name.toLowerCase()
out.write(command.toByteArray(charset("ASCII"))) out.write(command.toByteArray(charset("ASCII")))
for (i in command.length..11) { for (i in command.length..11) {
out.write(0x0) out.write(0x0)
} }
val payloadBytes = Encode.bytes(payload) val payloadBytes = Encode.bytes(item.payload)
// 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
@ -94,14 +91,14 @@ data class NetworkMessage(
Encode.int32(MAGIC, 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 = item.payload.command.name.toLowerCase()
out.put(command.toByteArray(charset("ASCII"))) out.put(command.toByteArray(charset("ASCII")))
for (i in command.length..11) { for (i in command.length..11) {
out.put(0.toByte()) out.put(0.toByte())
} }
val payloadBytes = Encode.bytes(payload) val payloadBytes = Encode.bytes(item.payload)
// 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
@ -115,6 +112,16 @@ data class NetworkMessage(
return payloadBytes return payloadBytes
} }
/**
* First 4 bytes of sha512(payload)
*/
private fun getChecksum(bytes: ByteArray): ByteArray {
val d = cryptography().sha512(bytes)
return byteArrayOf(d[0], d[1], d[2], d[3])
}
}
companion object { companion object {
/** /**
* Magic value indicating message origin network, and used to seek to next message when stream state is unknown * Magic value indicating message origin network, and used to seek to next message when stream state is unknown

View File

@ -39,36 +39,26 @@ data class ObjectMessage(
var nonce: ByteArray? = null, var nonce: ByteArray? = null,
val expiresTime: Long, val expiresTime: Long,
val payload: ObjectPayload, val payload: ObjectPayload,
val type: Long, val type: Long = payload.type?.number ?: throw IllegalArgumentException("payload must have type defined"),
/** /**
* The object's version * The object's version
*/ */
val version: Long, val version: Long = payload.version,
val stream: Long val stream: Long = payload.stream
) : MessagePayload { ) : MessagePayload {
override val command: MessagePayload.Command = MessagePayload.Command.OBJECT override val command: MessagePayload.Command = MessagePayload.Command.OBJECT
constructor(
nonce: ByteArray? = null,
expiresTime: Long,
payload: ObjectPayload,
stream: Long
) : this(
nonce,
expiresTime,
payload,
payload.type?.number ?: throw IllegalArgumentException("payload must have type defined"),
payload.version,
stream
)
val inventoryVector: InventoryVector val inventoryVector: InventoryVector
get() { get() {
return InventoryVector(Bytes.truncate(cryptography().doubleSha512( return InventoryVector(
Bytes.truncate(
cryptography().doubleSha512(
nonce ?: throw IllegalStateException("nonce must be set"), nonce ?: throw IllegalStateException("nonce must be set"),
payloadBytesWithoutNonce payloadBytesWithoutNonce
), 32)) ), 32
)
)
} }
private val isEncrypted: Boolean private val isEncrypted: Boolean
@ -81,8 +71,8 @@ data class ObjectMessage(
get() { get() {
try { try {
val out = ByteArrayOutputStream() val out = ByteArrayOutputStream()
writeHeaderWithoutNonce(out) writer.writeHeaderWithoutNonce(out)
payload.writeBytesToSign(out) payload.writer().writeBytesToSign(out)
return out.toByteArray() return out.toByteArray()
} catch (e: IOException) { } catch (e: IOException) {
throw ApplicationException(e) throw ApplicationException(e)
@ -131,28 +121,37 @@ data class ObjectMessage(
return cryptography().isSignatureValid(bytesToSign, payload.signature ?: return false, pubkey) return cryptography().isSignatureValid(bytesToSign, payload.signature ?: return false, pubkey)
} }
val payloadBytesWithoutNonce: ByteArray by lazy {
val out = ByteArrayOutputStream()
writer.writeHeaderWithoutNonce(out)
payload.writer().write(out)
out.toByteArray()
}
private val writer = Writer(this)
override fun writer(): StreamableWriter = writer
private class Writer(
private val item: ObjectMessage
) : StreamableWriter {
override fun write(out: OutputStream) { override fun write(out: OutputStream) {
out.write(nonce ?: ByteArray(8)) out.write(item.nonce ?: ByteArray(8))
out.write(payloadBytesWithoutNonce) out.write(item.payloadBytesWithoutNonce)
} }
override fun write(buffer: ByteBuffer) { override fun write(buffer: ByteBuffer) {
buffer.put(nonce ?: ByteArray(8)) buffer.put(item.nonce ?: ByteArray(8))
buffer.put(payloadBytesWithoutNonce) buffer.put(item.payloadBytesWithoutNonce)
} }
private fun writeHeaderWithoutNonce(out: OutputStream) { internal fun writeHeaderWithoutNonce(out: OutputStream) {
Encode.int64(expiresTime, out) Encode.int64(item.expiresTime, out)
Encode.int32(type, out) Encode.int32(item.type, out)
Encode.varInt(version, out) Encode.varInt(item.version, out)
Encode.varInt(stream, out) Encode.varInt(item.stream, out)
} }
val payloadBytesWithoutNonce: ByteArray by lazy {
val out = ByteArrayOutputStream()
writeHeaderWithoutNonce(out)
payload.write(out)
out.toByteArray()
} }
class Builder { class Builder {

View File

@ -35,6 +35,7 @@ import java.nio.ByteBuffer
import java.util.* import java.util.*
import java.util.Collections import java.util.Collections
import kotlin.collections.HashSet import kotlin.collections.HashSet
import kotlin.collections.LinkedHashSet
private fun message(encoding: Plaintext.Encoding, subject: String, body: String): ByteArray = when (encoding) { private fun message(encoding: Plaintext.Encoding, subject: String, body: String): ByteArray = when (encoding) {
SIMPLE -> "Subject:$subject\nBody:$body".toByteArray() SIMPLE -> "Subject:$subject\nBody:$body".toByteArray()
@ -64,7 +65,7 @@ class Plaintext private constructor(
val message: ByteArray, val message: ByteArray,
val ackData: ByteArray?, val ackData: ByteArray?,
ackMessage: Lazy<ObjectMessage?> = lazy { Factory.createAck(from, ackData, ttl) }, ackMessage: Lazy<ObjectMessage?> = lazy { Factory.createAck(from, ackData, ttl) },
val conversationId: UUID = UUID.randomUUID(), var conversationId: UUID = UUID.randomUUID(),
var inventoryVector: InventoryVector? = null, var inventoryVector: InventoryVector? = null,
var signature: ByteArray? = null, var signature: ByteArray? = null,
sent: Long? = null, sent: Long? = null,
@ -86,10 +87,10 @@ class Plaintext private constructor(
if (to == null) { if (to == null) {
return return
} }
if (this.to != null) { this.to?.let {
if (this.to!!.version != 0L) if (it.version != 0L)
throw IllegalStateException("Correct address already set") throw IllegalStateException("Correct address already set")
if (!Arrays.equals(this.to!!.ripe, to.ripe)) { if (!Arrays.equals(it.ripe, to.ripe)) {
throw IllegalArgumentException("RIPEs don't match") throw IllegalArgumentException("RIPEs don't match")
} }
} }
@ -186,7 +187,8 @@ class Plaintext private constructor(
Factory.getObjectMessage( Factory.getObjectMessage(
3, 3,
ByteArrayInputStream(ackMessage), ByteArrayInputStream(ackMessage),
ackMessage.size) ackMessage.size
)
} else null } else null
}, },
conversationId = conversationId, conversationId = conversationId,
@ -242,7 +244,8 @@ class Plaintext private constructor(
Factory.getObjectMessage( Factory.getObjectMessage(
3, 3,
ByteArrayInputStream(ackMsg), ByteArrayInputStream(ackMsg),
ackMsg.size) ackMsg.size
)
} else { } else {
Factory.createAck(builder.from!!, builder.ackData, builder.ttl) Factory.createAck(builder.from!!, builder.ackData, builder.ttl)
} }
@ -254,108 +257,12 @@ class Plaintext private constructor(
received = builder.received, received = builder.received,
initialHash = null, initialHash = null,
ttl = builder.ttl, ttl = builder.ttl,
labels = builder.labels, labels = LinkedHashSet(builder.labels),
status = builder.status ?: Status.RECEIVED status = builder.status ?: Status.RECEIVED
) { ) {
id = builder.id id = builder.id
} }
fun write(out: OutputStream, includeSignature: Boolean) {
Encode.varInt(from.version, out)
Encode.varInt(from.stream, out)
from.pubkey?.apply {
Encode.int32(behaviorBitfield, out)
out.write(signingKey, 1, 64)
out.write(encryptionKey, 1, 64)
if (from.version >= 3) {
Encode.varInt(nonceTrialsPerByte, out)
Encode.varInt(extraBytes, out)
}
} ?: {
Encode.int32(0, out)
val empty = ByteArray(64)
out.write(empty)
out.write(empty)
if (from.version >= 3) {
Encode.varInt(0, out)
Encode.varInt(0, out)
}
}.invoke()
if (type == MSG) {
out.write(to?.ripe ?: throw IllegalStateException("No recipient set for message"))
}
Encode.varInt(encodingCode, out)
Encode.varInt(message.size, out)
out.write(message)
if (type == MSG) {
if (to?.has(Feature.DOES_ACK) ?: false) {
val ack = ByteArrayOutputStream()
ackMessage?.write(ack)
Encode.varBytes(ack.toByteArray(), out)
} else {
Encode.varInt(0, out)
}
}
if (includeSignature) {
if (signature == null) {
Encode.varInt(0, out)
} else {
Encode.varBytes(signature!!, out)
}
}
}
fun write(buffer: ByteBuffer, includeSignature: Boolean) {
Encode.varInt(from.version, buffer)
Encode.varInt(from.stream, buffer)
if (from.pubkey == null) {
Encode.int32(0, buffer)
val empty = ByteArray(64)
buffer.put(empty)
buffer.put(empty)
if (from.version >= 3) {
Encode.varInt(0, buffer)
Encode.varInt(0, buffer)
}
} else {
Encode.int32(from.pubkey!!.behaviorBitfield, buffer)
buffer.put(from.pubkey!!.signingKey, 1, 64)
buffer.put(from.pubkey!!.encryptionKey, 1, 64)
if (from.version >= 3) {
Encode.varInt(from.pubkey!!.nonceTrialsPerByte, buffer)
Encode.varInt(from.pubkey!!.extraBytes, buffer)
}
}
if (type == MSG) {
buffer.put(to!!.ripe)
}
Encode.varInt(encodingCode, buffer)
Encode.varBytes(message, buffer)
if (type == MSG) {
if (to!!.has(Feature.DOES_ACK) && ackMessage != null) {
Encode.varBytes(Encode.bytes(ackMessage!!), buffer)
} else {
Encode.varInt(0, buffer)
}
}
if (includeSignature) {
val sig = signature
if (sig == null) {
Encode.varInt(0, buffer)
} else {
Encode.varBytes(sig, buffer)
}
}
}
override fun write(out: OutputStream) {
write(out, true)
}
override fun write(buffer: ByteBuffer) {
write(buffer, true)
}
fun updateNextTry() { fun updateNextTry() {
if (to != null) { if (to != null) {
if (nextTry == null) { if (nextTry == null) {
@ -374,28 +281,30 @@ class Plaintext private constructor(
get() { get() {
val s = Scanner(ByteArrayInputStream(message), "UTF-8") val s = Scanner(ByteArrayInputStream(message), "UTF-8")
val firstLine = s.nextLine() val firstLine = s.nextLine()
if (encodingCode == EXTENDED.code) { return when (encodingCode) {
if (Message.TYPE == extendedData?.type) { EXTENDED.code -> if (Message.TYPE == extendedData?.type) {
return (extendedData!!.content as? Message)?.subject (extendedData!!.content as? Message)?.subject
} else { } else {
return null null
} }
} else if (encodingCode == SIMPLE.code) { SIMPLE.code -> firstLine.substring("Subject:".length).trim { it <= ' ' }
return firstLine.substring("Subject:".length).trim { it <= ' ' } else -> {
} else if (firstLine.length > 50) { if (firstLine.length > 50) {
return firstLine.substring(0, 50).trim { it <= ' ' } + "..." firstLine.substring(0, 50).trim { it <= ' ' } + "..."
} else { } else {
return firstLine firstLine
}
}
} }
} }
val text: String? val text: String?
get() { get() {
if (encodingCode == EXTENDED.code) { if (encodingCode == EXTENDED.code) {
if (Message.TYPE == extendedData?.type) { return if (Message.TYPE == extendedData?.type) {
return (extendedData?.content as Message?)?.body (extendedData?.content as Message?)?.body
} else { } else {
return null null
} }
} else { } else {
val text = String(message) val text = String(message)
@ -418,20 +327,20 @@ class Plaintext private constructor(
val parents: List<InventoryVector> val parents: List<InventoryVector>
get() { get() {
val extendedData = extendedData ?: return emptyList() val extendedData = extendedData ?: return emptyList()
if (Message.TYPE == extendedData.type) { return if (Message.TYPE == extendedData.type) {
return (extendedData.content as Message).parents (extendedData.content as Message).parents
} else { } else {
return emptyList() emptyList()
} }
} }
val files: List<Attachment> val files: List<Attachment>
get() { get() {
val extendedData = extendedData ?: return emptyList() val extendedData = extendedData ?: return emptyList()
if (Message.TYPE == extendedData.type) { return if (Message.TYPE == extendedData.type) {
return (extendedData.content as Message).files (extendedData.content as Message).files
} else { } else {
return emptyList() emptyList()
} }
} }
@ -474,8 +383,8 @@ class Plaintext private constructor(
override fun toString(): String { override fun toString(): String {
val subject = subject val subject = subject
if (subject?.isNotEmpty() ?: false) { if (subject?.isNotEmpty() == true) {
return subject!! return subject
} else { } else {
return Strings.hex( return Strings.hex(
initialHash ?: return super.toString() initialHash ?: return super.toString()
@ -484,6 +393,7 @@ class Plaintext private constructor(
} }
enum class Encoding constructor(code: Long) { enum class Encoding constructor(code: Long) {
IGNORE(0), TRIVIAL(1), SIMPLE(2), EXTENDED(3); IGNORE(0), TRIVIAL(1), SIMPLE(2), EXTENDED(3);
var code: Long = 0 var code: Long = 0
@ -495,7 +405,8 @@ class Plaintext private constructor(
companion object { companion object {
@JvmStatic fun fromCode(code: Long): Encoding? { @JvmStatic
fun fromCode(code: Long): Encoding? {
for (e in values()) { for (e in values()) {
if (e.code == code) { if (e.code == code) {
return e return e
@ -503,12 +414,13 @@ class Plaintext private constructor(
} }
return null return null
} }
} }
} }
enum class Status { enum class Status {
DRAFT, DRAFT,
// For sent messages
PUBKEY_REQUESTED, PUBKEY_REQUESTED,
DOING_PROOF_OF_WORK, DOING_PROOF_OF_WORK,
SENT, SENT,
@ -517,36 +429,158 @@ class Plaintext private constructor(
} }
enum class Type { enum class Type {
MSG, BROADCAST MSG, BROADCAST
} }
fun writer(includeSignature: Boolean): StreamableWriter = Writer(this, includeSignature)
override fun writer(): StreamableWriter = Writer(this)
private class Writer(
private val item: Plaintext,
private val includeSignature: Boolean = true
) : StreamableWriter {
override fun write(out: OutputStream) {
Encode.varInt(item.from.version, out)
Encode.varInt(item.from.stream, out)
item.from.pubkey?.apply {
Encode.int32(behaviorBitfield, out)
out.write(signingKey, 1, 64)
out.write(encryptionKey, 1, 64)
if (item.from.version >= 3) {
Encode.varInt(nonceTrialsPerByte, out)
Encode.varInt(extraBytes, out)
}
} ?: {
Encode.int32(0, out)
val empty = ByteArray(64)
out.write(empty)
out.write(empty)
if (item.from.version >= 3) {
Encode.varInt(0, out)
Encode.varInt(0, out)
}
}.invoke()
if (item.type == MSG) {
// A draft without recipient is allowed, therefore this workaround.
item.to?.let { out.write(it.ripe) } ?: if (item.status == Status.DRAFT) {
out.write(ByteArray(20))
} else {
throw IllegalStateException("No recipient set for message")
}
}
Encode.varInt(item.encodingCode, out)
Encode.varInt(item.message.size, out)
out.write(item.message)
if (item.type == MSG) {
if (item.to?.has(Feature.DOES_ACK) == true) {
val ack = ByteArrayOutputStream()
item.ackMessage?.writer()?.write(ack)
Encode.varBytes(ack.toByteArray(), out)
} else {
Encode.varInt(0, out)
}
}
if (includeSignature) {
val sig = item.signature
if (sig == null) {
Encode.varInt(0, out)
} else {
Encode.varBytes(sig, out)
}
}
}
override fun write(buffer: ByteBuffer) {
Encode.varInt(item.from.version, buffer)
Encode.varInt(item.from.stream, buffer)
if (item.from.pubkey == null) {
Encode.int32(0, buffer)
val empty = ByteArray(64)
buffer.put(empty)
buffer.put(empty)
if (item.from.version >= 3) {
Encode.varInt(0, buffer)
Encode.varInt(0, buffer)
}
} else {
Encode.int32(item.from.pubkey!!.behaviorBitfield, buffer)
buffer.put(item.from.pubkey!!.signingKey, 1, 64)
buffer.put(item.from.pubkey!!.encryptionKey, 1, 64)
if (item.from.version >= 3) {
Encode.varInt(item.from.pubkey!!.nonceTrialsPerByte, buffer)
Encode.varInt(item.from.pubkey!!.extraBytes, buffer)
}
}
if (item.type == MSG) {
// A draft without recipient is allowed, therefore this workaround.
item.to?.let { buffer.put(it.ripe) } ?: if (item.status == Status.DRAFT) {
buffer.put(ByteArray(20))
} else {
throw IllegalStateException("No recipient set for message")
}
}
Encode.varInt(item.encodingCode, buffer)
Encode.varBytes(item.message, buffer)
if (item.type == MSG) {
if (item.to!!.has(Feature.DOES_ACK) && item.ackMessage != null) {
Encode.varBytes(Encode.bytes(item.ackMessage!!), buffer)
} else {
Encode.varInt(0, buffer)
}
}
if (includeSignature) {
val sig = item.signature
if (sig == null) {
Encode.varInt(0, buffer)
} else {
Encode.varBytes(sig, buffer)
}
}
}
}
class Builder(internal val type: Type) { class Builder(internal val type: Type) {
internal var id: Any? = null var id: Any? = null
internal var inventoryVector: InventoryVector? = null var inventoryVector: InventoryVector? = null
internal var from: BitmessageAddress? = null var from: BitmessageAddress? = null
internal var to: BitmessageAddress? = null var to: BitmessageAddress? = null
private var addressVersion: Long = 0 set(value) {
private var stream: Long = 0 if (value != null) {
private var behaviorBitfield: Int = 0 if (type != MSG && to != null)
private var publicSigningKey: ByteArray? = null throw IllegalArgumentException("recipient address only allowed for msg")
private var publicEncryptionKey: ByteArray? = null field = value
private var nonceTrialsPerByte: Long = 0 }
private var extraBytes: Long = 0 }
private var destinationRipe: ByteArray? = null var addressVersion: Long = 0
private var preventAck: Boolean = false var stream: Long = 0
internal var encoding: Long = 0 var behaviorBitfield: Int = 0
internal var message = ByteArray(0) var publicSigningKey: ByteArray? = null
internal var ackData: ByteArray? = null var publicEncryptionKey: ByteArray? = null
internal var ackMessage: ByteArray? = null var nonceTrialsPerByte: Long = 0
internal var signature: ByteArray? = null var extraBytes: Long = 0
internal var sent: Long? = null var destinationRipe: ByteArray? = null
internal var received: Long? = null set(value) {
internal var status: Status? = null if (type != MSG && value != null) throw IllegalArgumentException("ripe only allowed for msg")
internal val labels = LinkedHashSet<Label>() field = value
internal var ttl: Long = 0 }
internal var retries: Int = 0 var preventAck: Boolean = false
internal var nextTry: Long? = null var encoding: Long = 0
internal var conversation: UUID? = null var message = ByteArray(0)
var ackData: ByteArray? = null
var ackMessage: ByteArray? = null
var signature: ByteArray? = null
var sent: Long? = null
var received: Long? = null
var status: Status? = null
var labels: Collection<Label> = emptySet()
var ttl: Long = 0
var retries: Int = 0
var nextTry: Long? = null
var conversation: UUID? = null
fun id(id: Any): Builder { fun id(id: Any): Builder {
this.id = id this.id = id
@ -564,11 +598,7 @@ class Plaintext private constructor(
} }
fun to(address: BitmessageAddress?): Builder { fun to(address: BitmessageAddress?): Builder {
if (address != null) {
if (type != MSG && to != null)
throw IllegalArgumentException("recipient address only allowed for msg")
to = address to = address
}
return this return this
} }
@ -608,7 +638,6 @@ class Plaintext private constructor(
} }
fun destinationRipe(ripe: ByteArray?): Builder { fun destinationRipe(ripe: ByteArray?): Builder {
if (type != MSG && ripe != null) throw IllegalArgumentException("ripe only allowed for msg")
this.destinationRipe = ripe this.destinationRipe = ripe
return this return this
} }
@ -684,7 +713,7 @@ class Plaintext private constructor(
} }
fun labels(labels: Collection<Label>): Builder { fun labels(labels: Collection<Label>): Builder {
this.labels.addAll(labels) this.labels = labels
return this return this
} }
@ -710,7 +739,8 @@ class Plaintext private constructor(
internal fun prepare(): Builder { internal fun prepare(): Builder {
if (from == null) { if (from == null) {
from = BitmessageAddress(Factory.createPubkey( from = BitmessageAddress(
Factory.createPubkey(
addressVersion, addressVersion,
stream, stream,
publicSigningKey!!, publicSigningKey!!,
@ -718,7 +748,8 @@ class Plaintext private constructor(
nonceTrialsPerByte, nonceTrialsPerByte,
extraBytes, extraBytes,
behaviorBitfield behaviorBitfield
)) )
)
} }
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!!)
@ -726,7 +757,7 @@ class Plaintext private constructor(
if (preventAck) { if (preventAck) {
ackData = null ackData = null
ackMessage = null ackMessage = null
} else if (type == MSG && ackMessage == null && ackData == null) { } else if (type == MSG && ackMessage == null && ackData == null && to?.has(Feature.DOES_ACK) == true) {
ackData = cryptography().randomBytes(Msg.ACK_LENGTH) ackData = cryptography().randomBytes(Msg.ACK_LENGTH)
} }
if (ttl <= 0) { if (ttl <= 0) {
@ -735,6 +766,12 @@ class Plaintext private constructor(
return this return this
} }
@JvmSynthetic
inline fun build(block: Builder.() -> Unit): Plaintext {
block(this)
return build()
}
fun build(): Plaintext { fun build(): Plaintext {
return Plaintext(this) return Plaintext(this)
} }
@ -742,27 +779,54 @@ class Plaintext private constructor(
companion object { companion object {
@JvmStatic fun read(type: Type, `in`: InputStream): Plaintext { @JvmStatic
return readWithoutSignature(type, `in`) fun read(type: Type, input: InputStream): Plaintext {
.signature(Decode.varBytes(`in`)) return readWithoutSignature(type, input)
.signature(Decode.varBytes(input))
.received(UnixTime.now) .received(UnixTime.now)
.build() .build()
} }
@JvmStatic fun readWithoutSignature(type: Type, `in`: InputStream): Plaintext.Builder { @JvmStatic
val version = Decode.varInt(`in`) fun readWithoutSignature(type: Type, input: InputStream): Plaintext.Builder {
val version = Decode.varInt(input)
return Builder(type) return Builder(type)
.addressVersion(version) .addressVersion(version)
.stream(Decode.varInt(`in`)) .stream(Decode.varInt(input))
.behaviorBitfield(Decode.int32(`in`)) .behaviorBitfield(Decode.int32(input))
.publicSigningKey(Decode.bytes(`in`, 64)) .publicSigningKey(Decode.bytes(input, 64))
.publicEncryptionKey(Decode.bytes(`in`, 64)) .publicEncryptionKey(Decode.bytes(input, 64))
.nonceTrialsPerByte(if (version >= 3) Decode.varInt(`in`) else 0) .nonceTrialsPerByte(if (version >= 3) Decode.varInt(input) else 0)
.extraBytes(if (version >= 3) Decode.varInt(`in`) else 0) .extraBytes(if (version >= 3) Decode.varInt(input) else 0)
.destinationRipe(if (type == MSG) Decode.bytes(`in`, 20) else null) .destinationRipe(if (type == MSG) Decode.bytes(input, 20).let {
.encoding(Decode.varInt(`in`)) if (it.any { x -> x != 0.toByte() }) it else null
.message(Decode.varBytes(`in`)) } else null)
.ackMessage(if (type == MSG) Decode.varBytes(`in`) else null) .encoding(Decode.varInt(input))
.message(Decode.varBytes(input))
.ackMessage(if (type == MSG) Decode.varBytes(input) else null)
}
@JvmSynthetic
inline fun build(type: Type, block: Builder.() -> Unit): Plaintext {
val builder = Builder(type)
block(builder)
return builder.build()
}
} }
} }
data class Conversation(val id: UUID, val subject: String, val messages: List<Plaintext>) : Serializable {
val participants = messages
.map { it.from }
.filter { it.privateKey == null || it.isChan }
.distinct()
val extract: String by lazy {
messages.firstOrNull { m -> m.labels.any { l -> l.type==Label.Type.UNREAD } }?.text
?: messages.lastOrNull()?.text
?: ""
}
fun hasUnread() = messages.any { it.isUnread() }
} }

View File

@ -24,7 +24,27 @@ import java.nio.ByteBuffer
* An object that can be written to an [OutputStream] * An object that can be written to an [OutputStream]
*/ */
interface Streamable : Serializable { interface Streamable : Serializable {
fun write(out: OutputStream) fun writer(): StreamableWriter
}
interface SignedStreamable : Streamable {
override fun writer(): SignedStreamableWriter
}
interface EncryptedStreamable : SignedStreamable {
override fun writer(): EncryptedStreamableWriter
}
interface StreamableWriter: Serializable {
fun write(out: OutputStream)
fun write(buffer: ByteBuffer) fun write(buffer: ByteBuffer)
} }
interface SignedStreamableWriter : StreamableWriter {
fun writeBytesToSign(out: OutputStream)
}
interface EncryptedStreamableWriter : SignedStreamableWriter {
fun writeUnencrypted(out: OutputStream)
fun writeUnencrypted(buffer: ByteBuffer)
}

View File

@ -26,12 +26,12 @@ class VerAck : MessagePayload {
override val command: MessagePayload.Command = MessagePayload.Command.VERACK override val command: MessagePayload.Command = MessagePayload.Command.VERACK
override fun write(out: OutputStream) {
// 'verack' doesn't have any payload, so there is nothing to write // 'verack' doesn't have any payload, so there is nothing to write
} override fun writer(): StreamableWriter = EmptyWriter
override fun write(buffer: ByteBuffer) { internal object EmptyWriter : StreamableWriter {
// 'verack' doesn't have any payload, so there is nothing to write override fun write(out: OutputStream) = Unit
override fun write(buffer: ByteBuffer) = Unit
} }
companion object { companion object {

View File

@ -71,32 +71,36 @@ class Version constructor(
val streams: LongArray = longArrayOf(1) val streams: LongArray = longArrayOf(1)
) : MessagePayload { ) : MessagePayload {
fun provides(service: Service?): Boolean { fun provides(service: Service?) = service?.isEnabled(services) == true
return service != null && service.isEnabled(services)
}
override val command: MessagePayload.Command = MessagePayload.Command.VERSION override val command: MessagePayload.Command = MessagePayload.Command.VERSION
override fun writer(): StreamableWriter = Writer(this)
private class Writer(
private val item: Version
) : StreamableWriter {
override fun write(out: OutputStream) { override fun write(out: OutputStream) {
Encode.int32(version, out) Encode.int32(item.version, out)
Encode.int64(services, out) Encode.int64(item.services, out)
Encode.int64(timestamp, out) Encode.int64(item.timestamp, out)
addrRecv.write(out, true) item.addrRecv.writer(true).write(out)
addrFrom.write(out, true) item.addrFrom.writer(true).write(out)
Encode.int64(nonce, out) Encode.int64(item.nonce, out)
Encode.varString(userAgent, out) Encode.varString(item.userAgent, out)
Encode.varIntList(streams, out) Encode.varIntList(item.streams, out)
} }
override fun write(buffer: ByteBuffer) { override fun write(buffer: ByteBuffer) {
Encode.int32(version, buffer) Encode.int32(item.version, buffer)
Encode.int64(services, buffer) Encode.int64(item.services, buffer)
Encode.int64(timestamp, buffer) Encode.int64(item.timestamp, buffer)
addrRecv.write(buffer, true) item.addrRecv.writer(true).write(buffer)
addrFrom.write(buffer, true) item.addrFrom.writer(true).write(buffer)
Encode.int64(nonce, buffer) Encode.int64(item.nonce, buffer)
Encode.varString(userAgent, buffer) Encode.varString(item.userAgent, buffer)
Encode.varIntList(streams, buffer) Encode.varIntList(item.streams, buffer)
}
} }
class Builder { class Builder {
@ -187,9 +191,7 @@ class Version constructor(
// TODO: NODE_SSL(2); // TODO: NODE_SSL(2);
NODE_NETWORK(1); NODE_NETWORK(1);
fun isEnabled(flag: Long): Boolean { fun isEnabled(flag: Long) = (flag and this.flag) != 0L
return (flag and this.flag) != 0L
}
companion object { companion object {
fun getServiceFlag(vararg services: Service): Long { fun getServiceFlag(vararg services: Service): Long {

View File

@ -29,7 +29,12 @@ import java.util.*
* Users who are subscribed to the sending address will see the message appear in their inbox. * Users who are subscribed to the sending address will see the message appear in their inbox.
* Broadcasts are version 4 or 5. * Broadcasts are version 4 or 5.
*/ */
abstract class Broadcast protected constructor(version: Long, override val stream: Long, protected var encrypted: CryptoBox?, override var plaintext: Plaintext?) : ObjectPayload(version), Encrypted, PlaintextHolder { abstract class Broadcast protected constructor(
version: Long,
override val stream: Long,
protected var encrypted: CryptoBox?,
override var plaintext: Plaintext?
) : ObjectPayload(version), Encrypted, PlaintextHolder {
override val isSigned: Boolean = true override val isSigned: Boolean = true

View File

@ -17,6 +17,7 @@
package ch.dissem.bitmessage.entity.payload package ch.dissem.bitmessage.entity.payload
import ch.dissem.bitmessage.entity.Streamable import ch.dissem.bitmessage.entity.Streamable
import ch.dissem.bitmessage.entity.StreamableWriter
import ch.dissem.bitmessage.entity.valueobject.PrivateKey.Companion.PRIVATE_KEY_SIZE import ch.dissem.bitmessage.entity.valueobject.PrivateKey.Companion.PRIVATE_KEY_SIZE
import ch.dissem.bitmessage.exception.DecryptionFailedException import ch.dissem.bitmessage.exception.DecryptionFailedException
import ch.dissem.bitmessage.utils.* import ch.dissem.bitmessage.utils.*
@ -108,16 +109,37 @@ class CryptoBox : Streamable {
private fun calculateMac(key_m: ByteArray): ByteArray { private fun calculateMac(key_m: ByteArray): ByteArray {
val macData = ByteArrayOutputStream() val macData = ByteArrayOutputStream()
writeWithoutMAC(macData) writer.writeWithoutMAC(macData)
return cryptography().mac(key_m, macData.toByteArray()) return cryptography().mac(key_m, macData.toByteArray())
} }
private fun writeWithoutMAC(out: OutputStream) { private val writer = Writer(this)
out.write(initializationVector) override fun writer(): StreamableWriter = writer
Encode.int16(curveType, out)
writeCoordinateComponent(out, Points.getX(R)) private class Writer(
writeCoordinateComponent(out, Points.getY(R)) private val item: CryptoBox
out.write(encrypted) ) : StreamableWriter {
override fun write(out: OutputStream) {
writeWithoutMAC(out)
out.write(item.mac)
}
internal fun writeWithoutMAC(out: OutputStream) {
out.write(item.initializationVector)
Encode.int16(item.curveType, out)
writeCoordinateComponent(out, Points.getX(item.R))
writeCoordinateComponent(out, Points.getY(item.R))
out.write(item.encrypted)
}
override fun write(buffer: ByteBuffer) {
buffer.put(item.initializationVector)
Encode.int16(item.curveType, buffer)
writeCoordinateComponent(buffer, Points.getX(item.R))
writeCoordinateComponent(buffer, Points.getY(item.R))
buffer.put(item.encrypted)
buffer.put(item.mac)
} }
private fun writeCoordinateComponent(out: OutputStream, x: ByteArray) { private fun writeCoordinateComponent(out: OutputStream, x: ByteArray) {
@ -134,18 +156,6 @@ class CryptoBox : Streamable {
buffer.put(x, offset, length) buffer.put(x, offset, length)
} }
override fun write(out: OutputStream) {
writeWithoutMAC(out)
out.write(mac)
}
override fun write(buffer: ByteBuffer) {
buffer.put(initializationVector)
Encode.int16(curveType, buffer)
writeCoordinateComponent(buffer, Points.getX(R))
writeCoordinateComponent(buffer, Points.getY(R))
buffer.put(encrypted)
buffer.put(mac)
} }
class Builder { class Builder {
@ -187,15 +197,14 @@ class CryptoBox : Streamable {
return this return this
} }
fun build(): CryptoBox { fun build() = CryptoBox(this)
return CryptoBox(this)
}
} }
companion object { companion object {
private val LOG = LoggerFactory.getLogger(CryptoBox::class.java) private val LOG = LoggerFactory.getLogger(CryptoBox::class.java)
@JvmStatic fun read(stream: InputStream, length: Int): CryptoBox { @JvmStatic
fun read(stream: InputStream, length: Int): CryptoBox {
val counter = AccessCounter() val counter = AccessCounter()
return Builder() return Builder()
.IV(Decode.bytes(stream, 16, counter)) .IV(Decode.bytes(stream, 16, counter))

View File

@ -16,6 +16,7 @@
package ch.dissem.bitmessage.entity.payload package ch.dissem.bitmessage.entity.payload
import ch.dissem.bitmessage.entity.SignedStreamableWriter
import ch.dissem.bitmessage.utils.Decode import ch.dissem.bitmessage.utils.Decode
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
@ -30,14 +31,6 @@ class GenericPayload(version: Long, override val stream: Long, val data: ByteArr
override val type: ObjectType? = null override val type: ObjectType? = null
override fun write(out: OutputStream) {
out.write(data)
}
override fun write(buffer: ByteBuffer) {
buffer.put(data)
}
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 GenericPayload) return false if (other !is GenericPayload) return false
@ -52,9 +45,27 @@ class GenericPayload(version: Long, override val stream: Long, val data: ByteArr
return result return result
} }
override fun writer(): SignedStreamableWriter = Writer(this)
private class Writer(
private val item: GenericPayload
) : SignedStreamableWriter {
override fun write(out: OutputStream) {
out.write(item.data)
}
override fun write(buffer: ByteBuffer) {
buffer.put(item.data)
}
override fun writeBytesToSign(out: OutputStream) = Unit // nothing to do
}
companion object { companion object {
@JvmStatic fun read(version: Long, stream: Long, `is`: InputStream, length: Int): GenericPayload { @JvmStatic
return GenericPayload(version, stream, Decode.bytes(`is`, length)) fun read(version: Long, stream: Long, input: InputStream, length: Int) =
} GenericPayload(version, stream, Decode.bytes(input, length))
} }
} }

View File

@ -17,6 +17,7 @@
package ch.dissem.bitmessage.entity.payload package ch.dissem.bitmessage.entity.payload
import ch.dissem.bitmessage.entity.BitmessageAddress import ch.dissem.bitmessage.entity.BitmessageAddress
import ch.dissem.bitmessage.entity.SignedStreamableWriter
import ch.dissem.bitmessage.utils.Decode import ch.dissem.bitmessage.utils.Decode
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
@ -47,17 +48,28 @@ class GetPubkey : ObjectPayload {
this.ripeTag = ripeOrTag this.ripeTag = ripeOrTag
} }
override fun writer(): SignedStreamableWriter = Writer(this)
private class Writer(
private val item: GetPubkey
) : SignedStreamableWriter {
override fun write(out: OutputStream) { override fun write(out: OutputStream) {
out.write(ripeTag) out.write(item.ripeTag)
} }
override fun write(buffer: ByteBuffer) { override fun write(buffer: ByteBuffer) {
buffer.put(ripeTag) buffer.put(item.ripeTag)
}
override fun writeBytesToSign(out: OutputStream) = Unit // nothing to sign
} }
companion object { companion object {
@JvmStatic fun read(`is`: InputStream, stream: Long, length: Int, version: Long): GetPubkey { @JvmStatic
return GetPubkey(version, stream, Decode.bytes(`is`, length)) fun read(input: InputStream, stream: Long, length: Int, version: Long): GetPubkey {
return GetPubkey(version, stream, Decode.bytes(input, length))
} }
} }
} }

View File

@ -20,6 +20,7 @@ import ch.dissem.bitmessage.entity.Encrypted
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
import ch.dissem.bitmessage.entity.PlaintextHolder import ch.dissem.bitmessage.entity.PlaintextHolder
import ch.dissem.bitmessage.entity.SignedStreamableWriter
import ch.dissem.bitmessage.exception.DecryptionFailedException import ch.dissem.bitmessage.exception.DecryptionFailedException
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
@ -51,10 +52,6 @@ class Msg : ObjectPayload, Encrypted, PlaintextHolder {
override val isSigned: Boolean = true override val isSigned: Boolean = true
override fun writeBytesToSign(out: OutputStream) {
plaintext?.write(out, false) ?: throw IllegalStateException("no plaintext data available")
}
override var signature: ByteArray? override var signature: ByteArray?
get() = plaintext?.signature get() = plaintext?.signature
set(signature) { set(signature) {
@ -73,14 +70,6 @@ class Msg : ObjectPayload, Encrypted, PlaintextHolder {
override val isDecrypted: Boolean override val isDecrypted: Boolean
get() = plaintext != null get() = plaintext != null
override fun write(out: OutputStream) {
encrypted?.write(out) ?: throw IllegalStateException("Msg must be signed and encrypted before writing it.")
}
override fun write(buffer: ByteBuffer) {
encrypted?.write(buffer) ?: throw IllegalStateException("Msg must be signed and encrypted before writing it.")
}
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 Msg) return false if (other !is Msg) return false
@ -89,15 +78,34 @@ class Msg : ObjectPayload, Encrypted, PlaintextHolder {
return stream == other.stream && (encrypted == other.encrypted || plaintext == other.plaintext) return stream == other.stream && (encrypted == other.encrypted || plaintext == other.plaintext)
} }
override fun hashCode(): Int { override fun hashCode() = stream.toInt()
return stream.toInt()
override fun writer(): SignedStreamableWriter = Writer(this)
private class Writer(
private val item: Msg
) : SignedStreamableWriter {
val encryptedDataWriter = item.encrypted?.writer()
override fun write(out: OutputStream) {
encryptedDataWriter?.write(out) ?: throw IllegalStateException("Msg must be signed and encrypted before writing it.")
}
override fun write(buffer: ByteBuffer) {
encryptedDataWriter?.write(buffer) ?: throw IllegalStateException("Msg must be signed and encrypted before writing it.")
}
override fun writeBytesToSign(out: OutputStream) {
item.plaintext?.writer(false)?.write(out) ?: throw IllegalStateException("no plaintext data available")
}
} }
companion object { companion object {
val ACK_LENGTH = 32 val ACK_LENGTH = 32
@JvmStatic fun read(`in`: InputStream, stream: Long, length: Int): Msg { @JvmStatic
return Msg(stream, CryptoBox.read(`in`, length)) fun read(input: InputStream, stream: Long, length: Int) = Msg(stream, CryptoBox.read(input, length))
}
} }
} }

View File

@ -17,13 +17,14 @@
package ch.dissem.bitmessage.entity.payload package ch.dissem.bitmessage.entity.payload
import ch.dissem.bitmessage.entity.ObjectMessage import ch.dissem.bitmessage.entity.ObjectMessage
import ch.dissem.bitmessage.entity.SignedStreamable
import ch.dissem.bitmessage.entity.Streamable import ch.dissem.bitmessage.entity.Streamable
import java.io.OutputStream import java.io.OutputStream
/** /**
* The payload of an 'object' command. This is shared by the network. * The payload of an 'object' command. This is shared by the network.
*/ */
abstract class ObjectPayload protected constructor(val version: Long) : Streamable { abstract class ObjectPayload protected constructor(val version: Long) : SignedStreamable {
abstract val type: ObjectType? abstract val type: ObjectType?
@ -31,10 +32,6 @@ abstract class ObjectPayload protected constructor(val version: Long) : Streamab
open val isSigned: Boolean = false open val isSigned: Boolean = false
open fun writeBytesToSign(out: OutputStream) {
// nothing to do
}
/** /**
* @return the ECDSA signature which, as of protocol v3, covers the object header starting with the time, * @return the ECDSA signature which, as of protocol v3, covers the object header starting with the time,
* * appended with the data described in this table down to the extra_bytes. Therefore, this must * * appended with the data described in this table down to the extra_bytes. Therefore, this must

View File

@ -18,6 +18,8 @@ package ch.dissem.bitmessage.entity.payload
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE
import ch.dissem.bitmessage.entity.EncryptedStreamableWriter
import ch.dissem.bitmessage.entity.SignedStreamableWriter
import ch.dissem.bitmessage.utils.Singleton.cryptography import ch.dissem.bitmessage.utils.Singleton.cryptography
import java.io.OutputStream import java.io.OutputStream
import java.nio.ByteBuffer import java.nio.ByteBuffer
@ -42,13 +44,7 @@ abstract class Pubkey protected constructor(version: Long) : ObjectPayload(versi
open val extraBytes: Long = NETWORK_EXTRA_BYTES open val extraBytes: Long = NETWORK_EXTRA_BYTES
open fun writeUnencrypted(out: OutputStream) { abstract override fun writer(): EncryptedStreamableWriter
write(out)
}
open fun writeUnencrypted(buffer: ByteBuffer) {
write(buffer)
}
/** /**
* Bits 0 through 29 are yet undefined * Bits 0 through 29 are yet undefined

View File

@ -16,6 +16,7 @@
package ch.dissem.bitmessage.entity.payload package ch.dissem.bitmessage.entity.payload
import ch.dissem.bitmessage.entity.EncryptedStreamableWriter
import ch.dissem.bitmessage.utils.Decode import ch.dissem.bitmessage.utils.Decode
import ch.dissem.bitmessage.utils.Encode import ch.dissem.bitmessage.utils.Encode
import java.io.InputStream import java.io.InputStream
@ -25,21 +26,47 @@ import java.nio.ByteBuffer
/** /**
* A version 2 public key. * A version 2 public key.
*/ */
open class V2Pubkey constructor(version: Long, override val stream: Long, override val behaviorBitfield: Int, signingKey: ByteArray, encryptionKey: ByteArray) : Pubkey(version) { open class V2Pubkey constructor(
version: Long,
override val stream: Long,
override val behaviorBitfield: Int,
signingKey: ByteArray,
encryptionKey: ByteArray
) : Pubkey(version) {
override val signingKey: ByteArray = if (signingKey.size == 64) add0x04(signingKey) else signingKey override val signingKey: ByteArray = if (signingKey.size == 64) add0x04(signingKey) else signingKey
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 writer(): EncryptedStreamableWriter = Writer(this)
protected open class Writer(
private val item: V2Pubkey
) : EncryptedStreamableWriter {
override fun write(out: OutputStream) { override fun write(out: OutputStream) {
Encode.int32(behaviorBitfield, out) Encode.int32(item.behaviorBitfield, out)
out.write(signingKey, 1, 64) out.write(item.signingKey, 1, 64)
out.write(encryptionKey, 1, 64) out.write(item.encryptionKey, 1, 64)
} }
override fun write(buffer: ByteBuffer) { override fun write(buffer: ByteBuffer) {
Encode.int32(behaviorBitfield, buffer) Encode.int32(item.behaviorBitfield, buffer)
buffer.put(signingKey, 1, 64) buffer.put(item.signingKey, 1, 64)
buffer.put(encryptionKey, 1, 64) buffer.put(item.encryptionKey, 1, 64)
}
override fun writeBytesToSign(out: OutputStream) {
// Nothing to do
}
override fun writeUnencrypted(out: OutputStream) {
write(out)
}
override fun writeUnencrypted(buffer: ByteBuffer) {
write(buffer)
}
} }
class Builder { class Builder {
@ -80,13 +107,13 @@ open class V2Pubkey constructor(version: Long, override val stream: Long, overri
} }
companion object { companion object {
@JvmStatic fun read(`in`: InputStream, stream: Long): V2Pubkey { @JvmStatic fun read(input: InputStream, stream: Long): V2Pubkey {
return V2Pubkey( return V2Pubkey(
version = 2, version = 2,
stream = stream, stream = stream,
behaviorBitfield = Decode.uint32(`in`).toInt(), behaviorBitfield = Decode.uint32(input).toInt(),
signingKey = Decode.bytes(`in`, 64), signingKey = Decode.bytes(input, 64),
encryptionKey = Decode.bytes(`in`, 64) encryptionKey = Decode.bytes(input, 64)
) )
} }
} }

View File

@ -16,6 +16,7 @@
package ch.dissem.bitmessage.entity.payload package ch.dissem.bitmessage.entity.payload
import ch.dissem.bitmessage.entity.EncryptedStreamableWriter
import ch.dissem.bitmessage.utils.Decode import ch.dissem.bitmessage.utils.Decode
import ch.dissem.bitmessage.utils.Encode import ch.dissem.bitmessage.utils.Encode
import java.io.InputStream import java.io.InputStream
@ -34,32 +35,8 @@ class V3Pubkey protected constructor(
override var signature: ByteArray? = null override var signature: ByteArray? = null
) : V2Pubkey(version, stream, behaviorBitfield, signingKey, encryptionKey) { ) : V2Pubkey(version, stream, behaviorBitfield, signingKey, encryptionKey) {
override fun write(out: OutputStream) {
writeBytesToSign(out)
Encode.varBytes(
signature ?: throw IllegalStateException("signature not available"),
out
)
}
override fun write(buffer: ByteBuffer) {
super.write(buffer)
Encode.varInt(nonceTrialsPerByte, buffer)
Encode.varInt(extraBytes, buffer)
Encode.varBytes(
signature ?: throw IllegalStateException("signature not available"),
buffer
)
}
override val isSigned: Boolean = true override val isSigned: Boolean = true
override fun writeBytesToSign(out: OutputStream) {
super.write(out)
Encode.varInt(nonceTrialsPerByte, out)
Encode.varInt(extraBytes, out)
}
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 V3Pubkey) return false if (other !is V3Pubkey) return false
@ -75,6 +52,37 @@ class V3Pubkey protected constructor(
return Objects.hash(nonceTrialsPerByte, extraBytes) return Objects.hash(nonceTrialsPerByte, extraBytes)
} }
override fun writer(): EncryptedStreamableWriter = Writer(this)
protected open class Writer(
private val item: V3Pubkey
) : V2Pubkey.Writer(item) {
override fun write(out: OutputStream) {
writeBytesToSign(out)
Encode.varBytes(
item.signature ?: throw IllegalStateException("signature not available"),
out
)
}
override fun write(buffer: ByteBuffer) {
super.write(buffer)
Encode.varInt(item.nonceTrialsPerByte, buffer)
Encode.varInt(item.extraBytes, buffer)
Encode.varBytes(
item.signature ?: throw IllegalStateException("signature not available"),
buffer
)
}
override fun writeBytesToSign(out: OutputStream) {
super.write(out)
Encode.varInt(item.nonceTrialsPerByte, out)
Encode.varInt(item.extraBytes, out)
}
}
class Builder { class Builder {
private var streamNumber: Long = 0 private var streamNumber: Long = 0
private var behaviorBitfield: Int = 0 private var behaviorBitfield: Int = 0
@ -134,16 +142,16 @@ class V3Pubkey protected constructor(
} }
companion object { companion object {
@JvmStatic fun read(`is`: InputStream, stream: Long): V3Pubkey { @JvmStatic fun read(input: InputStream, stream: Long): V3Pubkey {
return V3Pubkey( return V3Pubkey(
version = 3, version = 3,
stream = stream, stream = stream,
behaviorBitfield = Decode.int32(`is`), behaviorBitfield = Decode.int32(input),
signingKey = Decode.bytes(`is`, 64), signingKey = Decode.bytes(input, 64),
encryptionKey = Decode.bytes(`is`, 64), encryptionKey = Decode.bytes(input, 64),
nonceTrialsPerByte = Decode.varInt(`is`), nonceTrialsPerByte = Decode.varInt(input),
extraBytes = Decode.varInt(`is`), extraBytes = Decode.varInt(input),
signature = Decode.varBytes(`is`) signature = Decode.varBytes(input)
) )
} }
} }

View File

@ -18,7 +18,7 @@ package ch.dissem.bitmessage.entity.payload
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.SignedStreamableWriter
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
import java.nio.ByteBuffer import java.nio.ByteBuffer
@ -38,22 +38,28 @@ open class V4Broadcast : Broadcast {
throw IllegalArgumentException("Address version 3 or older expected, but was " + senderAddress.version) throw IllegalArgumentException("Address version 3 or older expected, but was " + senderAddress.version)
} }
override fun writer(): SignedStreamableWriter = Writer(this)
protected open class Writer(
private val item: V4Broadcast
) : SignedStreamableWriter {
override fun writeBytesToSign(out: OutputStream) { override fun writeBytesToSign(out: OutputStream) {
plaintext?.write(out, false) ?: throw IllegalStateException("no plaintext data available") item.plaintext?.writer(false)?.write(out) ?: throw IllegalStateException("no plaintext data available")
} }
override fun write(out: OutputStream) { override fun write(out: OutputStream) {
encrypted?.write(out) ?: throw IllegalStateException("broadcast not encrypted") item.encrypted?.writer()?.write(out) ?: throw IllegalStateException("broadcast not encrypted")
} }
override fun write(buffer: ByteBuffer) { override fun write(buffer: ByteBuffer) {
encrypted?.write(buffer) ?: throw IllegalStateException("broadcast not encrypted") item.encrypted?.writer()?.write(buffer) ?: throw IllegalStateException("broadcast not encrypted")
}
} }
companion object { companion object {
@JvmStatic fun read(`in`: InputStream, stream: Long, length: Int): V4Broadcast { @JvmStatic
return V4Broadcast(4, stream, CryptoBox.read(`in`, length), null) fun read(input: InputStream, stream: Long, length: Int) =
} V4Broadcast(4, stream, CryptoBox.read(input, length), null)
} }
} }

View File

@ -18,6 +18,7 @@ package ch.dissem.bitmessage.entity.payload
import ch.dissem.bitmessage.entity.BitmessageAddress import ch.dissem.bitmessage.entity.BitmessageAddress
import ch.dissem.bitmessage.entity.Encrypted import ch.dissem.bitmessage.entity.Encrypted
import ch.dissem.bitmessage.entity.EncryptedStreamableWriter
import ch.dissem.bitmessage.exception.DecryptionFailedException import ch.dissem.bitmessage.exception.DecryptionFailedException
import ch.dissem.bitmessage.utils.Decode import ch.dissem.bitmessage.utils.Decode
import java.io.InputStream import java.io.InputStream
@ -63,29 +64,6 @@ class V4Pubkey : Pubkey, Encrypted {
override val isDecrypted: Boolean override val isDecrypted: Boolean
get() = decrypted != null get() = decrypted != null
override fun write(out: OutputStream) {
out.write(tag)
encrypted?.write(out) ?: throw IllegalStateException("pubkey is encrypted")
}
override fun write(buffer: ByteBuffer) {
buffer.put(tag)
encrypted?.write(buffer) ?: throw IllegalStateException("pubkey is encrypted")
}
override fun writeUnencrypted(out: OutputStream) {
decrypted?.write(out) ?: throw IllegalStateException("pubkey is encrypted")
}
override fun writeUnencrypted(buffer: ByteBuffer) {
decrypted?.write(buffer) ?: throw IllegalStateException("pubkey is encrypted")
}
override fun writeBytesToSign(out: OutputStream) {
out.write(tag)
decrypted?.writeBytesToSign(out) ?: throw IllegalStateException("pubkey is encrypted")
}
override val signingKey: ByteArray override val signingKey: ByteArray
get() = decrypted?.signingKey ?: throw IllegalStateException("pubkey is encrypted") get() = decrypted?.signingKey ?: throw IllegalStateException("pubkey is encrypted")
@ -126,14 +104,45 @@ class V4Pubkey : Pubkey, Encrypted {
return result return result
} }
override fun writer(): EncryptedStreamableWriter = Writer(this)
private class Writer(
val item: V4Pubkey
) : EncryptedStreamableWriter {
override fun write(out: OutputStream) {
out.write(item.tag)
item.encrypted?.writer()?.write(out) ?: throw IllegalStateException("pubkey is encrypted")
}
override fun write(buffer: ByteBuffer) {
buffer.put(item.tag)
item.encrypted?.writer()?.write(buffer) ?: throw IllegalStateException("pubkey is encrypted")
}
override fun writeUnencrypted(out: OutputStream) {
item.decrypted?.writer()?.write(out) ?: throw IllegalStateException("pubkey is encrypted")
}
override fun writeUnencrypted(buffer: ByteBuffer) {
item.decrypted?.writer()?.write(buffer) ?: throw IllegalStateException("pubkey is encrypted")
}
override fun writeBytesToSign(out: OutputStream) {
out.write(item.tag)
item.decrypted?.writer()?.writeBytesToSign(out) ?: throw IllegalStateException("pubkey is encrypted")
}
}
companion object { companion object {
@JvmStatic fun read(`in`: InputStream, stream: Long, length: Int, encrypted: Boolean): V4Pubkey { @JvmStatic
if (encrypted) fun read(input: InputStream, stream: Long, length: Int, encrypted: Boolean) = if (encrypted) {
return V4Pubkey(stream, V4Pubkey(stream,
Decode.bytes(`in`, 32), Decode.bytes(input, 32),
CryptoBox.read(`in`, length - 32)) CryptoBox.read(input, length - 32))
else } else {
return V4Pubkey(V3Pubkey.read(`in`, stream)) V4Pubkey(V3Pubkey.read(input, stream))
} }
} }
} }

View File

@ -18,6 +18,7 @@ package ch.dissem.bitmessage.entity.payload
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.SignedStreamableWriter
import ch.dissem.bitmessage.utils.Decode import ch.dissem.bitmessage.utils.Decode
import java.io.InputStream import java.io.InputStream
@ -40,19 +41,27 @@ class V5Broadcast : V4Broadcast {
this.tag = senderAddress.tag ?: throw IllegalStateException("version 4 address without tag") this.tag = senderAddress.tag ?: throw IllegalStateException("version 4 address without tag")
} }
override fun writer(): SignedStreamableWriter = Writer(this)
private class Writer(
private val item: V5Broadcast
) : V4Broadcast.Writer(item) {
override fun writeBytesToSign(out: OutputStream) { override fun writeBytesToSign(out: OutputStream) {
out.write(tag) out.write(item.tag)
super.writeBytesToSign(out) super.writeBytesToSign(out)
} }
override fun write(out: OutputStream) { override fun write(out: OutputStream) {
out.write(tag) out.write(item.tag)
super.write(out) super.write(out)
} }
}
companion object { companion object {
@JvmStatic fun read(`is`: InputStream, stream: Long, length: Int): V5Broadcast { @JvmStatic
return V5Broadcast(stream, Decode.bytes(`is`, 32), CryptoBox.read(`is`, length - 32)) fun read(input: InputStream, stream: Long, length: Int) =
} V5Broadcast(stream, Decode.bytes(input, 32), CryptoBox.read(input, length - 32))
} }
} }

View File

@ -17,6 +17,7 @@
package ch.dissem.bitmessage.entity.valueobject package ch.dissem.bitmessage.entity.valueobject
import ch.dissem.bitmessage.entity.Streamable import ch.dissem.bitmessage.entity.Streamable
import ch.dissem.bitmessage.entity.StreamableWriter
import ch.dissem.bitmessage.utils.Strings import ch.dissem.bitmessage.utils.Strings
import java.io.OutputStream import java.io.OutputStream
import java.nio.ByteBuffer import java.nio.ByteBuffer
@ -39,20 +40,29 @@ data class InventoryVector constructor(
return Arrays.hashCode(hash) return Arrays.hashCode(hash)
} }
override fun write(out: OutputStream) {
out.write(hash)
}
override fun write(buffer: ByteBuffer) {
buffer.put(hash)
}
override fun toString(): String { override fun toString(): String {
return Strings.hex(hash) return Strings.hex(hash)
} }
override fun writer(): StreamableWriter = Writer(this)
private class Writer(
private val item: InventoryVector
) : StreamableWriter {
override fun write(out: OutputStream) {
out.write(item.hash)
}
override fun write(buffer: ByteBuffer) {
buffer.put(item.hash)
}
}
companion object { companion object {
@JvmStatic fun fromHash(hash: ByteArray?): InventoryVector? { @JvmStatic
fun fromHash(hash: ByteArray?): InventoryVector? {
return InventoryVector( return InventoryVector(
hash ?: return null hash ?: return null
) )

View File

@ -17,6 +17,7 @@
package ch.dissem.bitmessage.entity.valueobject package ch.dissem.bitmessage.entity.valueobject
import ch.dissem.bitmessage.entity.Streamable import ch.dissem.bitmessage.entity.Streamable
import ch.dissem.bitmessage.entity.StreamableWriter
import ch.dissem.bitmessage.entity.Version import ch.dissem.bitmessage.entity.Version
import ch.dissem.bitmessage.utils.Encode import ch.dissem.bitmessage.utils.Encode
import ch.dissem.bitmessage.utils.UnixTime import ch.dissem.bitmessage.utils.UnixTime
@ -77,9 +78,7 @@ data class NetworkAddress(
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.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
@ -98,32 +97,40 @@ data class NetworkAddress(
return "[" + toInetAddress() + "]:" + port return "[" + toInetAddress() + "]:" + port
} }
override fun write(out: OutputStream) { fun writer(light: Boolean): StreamableWriter = Writer(
write(out, false) item = this,
} light = light
)
fun write(out: OutputStream, light: Boolean) { override fun writer(): StreamableWriter = Writer(
item = this
)
private class Writer(
private val item: NetworkAddress,
private val light: Boolean = false
) : StreamableWriter {
override fun write(out: OutputStream) {
if (!light) { if (!light) {
Encode.int64(time, out) Encode.int64(item.time, out)
Encode.int32(stream, out) Encode.int32(item.stream, out)
} }
Encode.int64(services, out) Encode.int64(item.services, out)
out.write(IPv6) out.write(item.IPv6)
Encode.int16(port, out) Encode.int16(item.port, out)
} }
override fun write(buffer: ByteBuffer) { override fun write(buffer: ByteBuffer) {
write(buffer, false) if (!light) {
Encode.int64(item.time, buffer)
Encode.int32(item.stream, buffer)
}
Encode.int64(item.services, buffer)
buffer.put(item.IPv6)
Encode.int16(item.port, buffer)
} }
fun write(buffer: ByteBuffer, light: Boolean) {
if (!light) {
Encode.int64(time, buffer)
Encode.int32(stream, buffer)
}
Encode.int64(services, buffer)
buffer.put(IPv6)
Encode.int16(port, buffer)
} }
class Builder { class Builder {
@ -194,6 +201,7 @@ data class NetworkAddress(
} }
companion object { companion object {
@JvmField val ANY = NetworkAddress(time = 0, stream = 0, services = 0, IPv6 = ByteArray(16), port = 0) @JvmField
val ANY = NetworkAddress(time = 0, stream = 0, services = 0, IPv6 = ByteArray(16), port = 0)
} }
} }

View File

@ -19,6 +19,7 @@ package ch.dissem.bitmessage.entity.valueobject
import ch.dissem.bitmessage.InternalContext import ch.dissem.bitmessage.InternalContext
import ch.dissem.bitmessage.entity.BitmessageAddress import ch.dissem.bitmessage.entity.BitmessageAddress
import ch.dissem.bitmessage.entity.Streamable import ch.dissem.bitmessage.entity.Streamable
import ch.dissem.bitmessage.entity.StreamableWriter
import ch.dissem.bitmessage.entity.payload.Pubkey import ch.dissem.bitmessage.entity.payload.Pubkey
import ch.dissem.bitmessage.exception.ApplicationException import ch.dissem.bitmessage.exception.ApplicationException
import ch.dissem.bitmessage.factory.Factory import ch.dissem.bitmessage.factory.Factory
@ -66,7 +67,52 @@ data class PrivateKey(
builder.nonceTrialsPerByte, builder.extraBytes, *builder.features) builder.nonceTrialsPerByte, builder.extraBytes, *builder.features)
) )
private class Builder internal constructor(internal val version: Long, internal val stream: Long, internal val shorter: Boolean) { override fun equals(other: Any?) = other is PrivateKey
&& Arrays.equals(privateEncryptionKey, other.privateEncryptionKey)
&& Arrays.equals(privateSigningKey, other.privateSigningKey)
&& pubkey == other.pubkey
override fun hashCode() = pubkey.hashCode()
override fun writer(): StreamableWriter = Writer(this)
private class Writer(
private val item: PrivateKey
) : StreamableWriter {
override fun write(out: OutputStream) {
Encode.varInt(item.pubkey.version, out)
Encode.varInt(item.pubkey.stream, out)
val baos = ByteArrayOutputStream()
item.pubkey.writer().writeUnencrypted(baos)
Encode.varInt(baos.size(), out)
out.write(baos.toByteArray())
Encode.varBytes(item.privateSigningKey, out)
Encode.varBytes(item.privateEncryptionKey, out)
}
override fun write(buffer: ByteBuffer) {
Encode.varInt(item.pubkey.version, buffer)
Encode.varInt(item.pubkey.stream, buffer)
try {
val baos = ByteArrayOutputStream()
item.pubkey.writer().writeUnencrypted(baos)
Encode.varBytes(baos.toByteArray(), buffer)
} catch (e: IOException) {
throw ApplicationException(e)
}
Encode.varBytes(item.privateSigningKey, buffer)
Encode.varBytes(item.privateEncryptionKey, buffer)
}
}
private class Builder internal constructor(
internal val version: Long,
internal val stream: Long,
internal val shorter: Boolean
) {
internal var seed: ByteArray? = null internal var seed: ByteArray? = null
internal var nextNonce: Long = 0 internal var nextNonce: Long = 0
@ -129,44 +175,12 @@ data class PrivateKey(
} }
} }
override fun write(out: OutputStream) {
Encode.varInt(pubkey.version, out)
Encode.varInt(pubkey.stream, out)
val baos = ByteArrayOutputStream()
pubkey.writeUnencrypted(baos)
Encode.varInt(baos.size(), out)
out.write(baos.toByteArray())
Encode.varBytes(privateSigningKey, out)
Encode.varBytes(privateEncryptionKey, out)
}
override fun write(buffer: ByteBuffer) {
Encode.varInt(pubkey.version, buffer)
Encode.varInt(pubkey.stream, buffer)
try {
val baos = ByteArrayOutputStream()
pubkey.writeUnencrypted(baos)
Encode.varBytes(baos.toByteArray(), buffer)
} catch (e: IOException) {
throw ApplicationException(e)
}
Encode.varBytes(privateSigningKey, buffer)
Encode.varBytes(privateEncryptionKey, buffer)
}
override fun equals(other: Any?) = other is PrivateKey
&& Arrays.equals(privateEncryptionKey, other.privateEncryptionKey)
&& Arrays.equals(privateSigningKey, other.privateSigningKey)
&& pubkey == other.pubkey
override fun hashCode() = pubkey.hashCode()
companion object { companion object {
@JvmField val PRIVATE_KEY_SIZE = 32 @JvmField
val PRIVATE_KEY_SIZE = 32
@JvmStatic fun deterministic(passphrase: String, numberOfAddresses: Int, version: Long, stream: Long, shorter: Boolean): List<PrivateKey> { @JvmStatic
fun deterministic(passphrase: String, numberOfAddresses: Int, version: Long, stream: Long, shorter: Boolean): List<PrivateKey> {
val result = ArrayList<PrivateKey>(numberOfAddresses) val result = ArrayList<PrivateKey>(numberOfAddresses)
val builder = Builder(version, stream, shorter).seed(passphrase) val builder = Builder(version, stream, shorter).seed(passphrase)
for (i in 0..numberOfAddresses - 1) { for (i in 0..numberOfAddresses - 1) {
@ -176,13 +190,14 @@ data class PrivateKey(
return result return result
} }
@JvmStatic fun read(`is`: InputStream): PrivateKey { @JvmStatic
val version = Decode.varInt(`is`).toInt() fun read(input: InputStream): PrivateKey {
val stream = Decode.varInt(`is`) val version = Decode.varInt(input).toInt()
val len = Decode.varInt(`is`).toInt() val stream = Decode.varInt(input)
val pubkey = Factory.readPubkey(version.toLong(), stream, `is`, len, false) ?: throw ApplicationException("Unknown pubkey version encountered") val len = Decode.varInt(input).toInt()
val signingKey = Decode.varBytes(`is`) val pubkey = Factory.readPubkey(version.toLong(), stream, input, len, false) ?: throw ApplicationException("Unknown pubkey version encountered")
val encryptionKey = Decode.varBytes(`is`) val signingKey = Decode.varBytes(input)
val encryptionKey = Decode.varBytes(input)
return PrivateKey(signingKey, encryptionKey, pubkey) return PrivateKey(signingKey, encryptionKey, pubkey)
} }
} }

View File

@ -37,33 +37,33 @@ import java.util.*
data class Message constructor( data class Message constructor(
val subject: String, val subject: String,
val body: String, val body: String,
val parents: List<InventoryVector>, val parents: List<InventoryVector> = emptyList(),
val files: List<Attachment> val files: List<Attachment> = emptyList()
) : ExtendedEncoding.ExtendedType { ) : ExtendedEncoding.ExtendedType {
override val type: String = TYPE override val type: String = TYPE
override fun pack(): MPMap<MPString, MPType<*>> { override fun pack(): MPMap<MPString, MPType<*>> {
val result = MPMap<MPString, MPType<*>>() val result = MPMap<MPString, MPType<*>>()
result.put(mp(""), mp(TYPE)) result["".mp] = TYPE.mp
result.put(mp("subject"), mp(subject)) result["subject".mp] = subject.mp
result.put(mp("body"), mp(body)) result["body".mp] = body.mp
if (!files.isEmpty()) { if (!files.isEmpty()) {
val items = MPArray<MPMap<MPString, MPType<*>>>() val items = MPArray<MPMap<MPString, MPType<*>>>()
result.put(mp("files"), items) result["files".mp] = items
for (file in files) { for (file in files) {
val item = MPMap<MPString, MPType<*>>() val item = MPMap<MPString, MPType<*>>()
item.put(mp("name"), mp(file.name)) item["name".mp] = file.name.mp
item.put(mp("data"), mp(*file.data)) item["data".mp] = file.data.mp
item.put(mp("type"), mp(file.type)) item["type".mp] = file.type.mp
item.put(mp("disposition"), mp(file.disposition.name)) item["disposition".mp] = file.disposition.name.mp
items.add(item) items.add(item)
} }
} }
if (!parents.isEmpty()) { if (!parents.isEmpty()) {
val items = MPArray<MPBinary>() val items = MPArray<MPBinary>()
result.put(mp("parents"), items) result["parents".mp] = items
for ((hash) in parents) { for ((hash) in parents) {
items.add(mp(*hash)) items.add(mp(*hash))
} }
@ -139,26 +139,26 @@ data class Message constructor(
override val type: String = TYPE override val type: String = TYPE
override fun unpack(map: MPMap<MPString, MPType<*>>): Message { override fun unpack(map: MPMap<MPString, MPType<*>>): Message {
val subject = str(map[mp("subject")]) ?: "" val subject = str(map["subject".mp]) ?: ""
val body = str(map[mp("body")]) ?: "" val body = str(map["body".mp]) ?: ""
val parents = LinkedList<InventoryVector>() val parents = LinkedList<InventoryVector>()
val files = LinkedList<Attachment>() val files = LinkedList<Attachment>()
val mpParents = map[mp("parents")] as? MPArray<*> val mpParents = map["parents".mp] as? MPArray<*>
for (parent in mpParents ?: emptyList<MPArray<MPBinary>>()) { for (parent in mpParents ?: emptyList<MPArray<MPBinary>>()) {
parents.add(InventoryVector.fromHash( parents.add(InventoryVector.fromHash(
(parent as? MPBinary)?.value ?: continue (parent as? MPBinary)?.value ?: continue
) ?: continue) ) ?: continue)
} }
val mpFiles = map[mp("files")] as? MPArray<*> val mpFiles = map["files".mp] as? MPArray<*>
for (item in mpFiles ?: emptyList<Any>()) { for (item in mpFiles ?: emptyList<Any>()) {
if (item is MPMap<*, *>) { if (item is MPMap<*, *>) {
val b = Attachment.Builder() val b = Attachment.Builder()
b.name(str(item[mp("name")])!!) b.name(str(item["name".mp])!!)
b.data( b.data(
bin(item[mp("data")] ?: continue) ?: continue bin(item["data".mp] ?: continue) ?: continue
) )
b.type(str(item[mp("type")])!!) b.type(str(item["type".mp])!!)
val disposition = str(item[mp("disposition")]) val disposition = str(item["disposition".mp])
if ("inline" == disposition) { if ("inline" == disposition) {
b.inline() b.inline()
} else if ("attachment" == disposition) { } else if ("attachment" == disposition) {
@ -179,6 +179,6 @@ data class Message constructor(
companion object { companion object {
private val LOG = LoggerFactory.getLogger(Message::class.java) private val LOG = LoggerFactory.getLogger(Message::class.java)
val TYPE = "message" const val TYPE = "message"
} }
} }

View File

@ -35,9 +35,9 @@ data class Vote constructor(val msgId: InventoryVector, val vote: String) : Exte
override fun pack(): MPMap<MPString, MPType<*>> { override fun pack(): MPMap<MPString, MPType<*>> {
val result = MPMap<MPString, MPType<*>>() val result = MPMap<MPString, MPType<*>>()
result.put(mp(""), mp(TYPE)) result.put("".mp, TYPE.mp)
result.put(mp("msgId"), mp(*msgId.hash)) result.put("msgId".mp, msgId.hash.mp)
result.put(mp("vote"), mp(vote)) result.put("vote".mp, vote.mp)
return result return result
} }
@ -77,13 +77,14 @@ data class Vote constructor(val msgId: InventoryVector, val vote: String) : Exte
get() = TYPE get() = TYPE
override fun unpack(map: MPMap<MPString, MPType<*>>): Vote { override fun unpack(map: MPMap<MPString, MPType<*>>): Vote {
val msgId = InventoryVector.fromHash((map[mp("msgId")] as? MPBinary)?.value) ?: throw IllegalArgumentException("data doesn't contain proper msgId") val msgId = InventoryVector.fromHash((map["msgId".mp] as? MPBinary)?.value) ?: throw IllegalArgumentException("data doesn't contain proper msgId")
val vote = str(map[mp("vote")]) ?: throw IllegalArgumentException("no vote given") val vote = str(map["vote".mp]) ?: throw IllegalArgumentException("no vote given")
return Vote(msgId, vote) return Vote(msgId, vote)
} }
} }
companion object { companion object {
@JvmField val TYPE = "vote" @JvmField
val TYPE = "vote"
} }
} }

View File

@ -1,77 +0,0 @@
/*
* 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.
*/
package ch.dissem.bitmessage.factory
import ch.dissem.bitmessage.constants.Network.HEADER_SIZE
import ch.dissem.bitmessage.constants.Network.MAX_PAYLOAD_SIZE
import org.slf4j.LoggerFactory
import java.nio.ByteBuffer
import java.util.*
/**
* A pool for [ByteBuffer]s. As they may use up a lot of memory,
* they should be reused as efficiently as possible.
*/
object BufferPool {
private val LOG = LoggerFactory.getLogger(BufferPool::class.java)
private val pools = mapOf(
HEADER_SIZE to Stack<ByteBuffer>(),
54 to Stack<ByteBuffer>(),
1000 to Stack<ByteBuffer>(),
60000 to Stack<ByteBuffer>(),
MAX_PAYLOAD_SIZE to Stack<ByteBuffer>()
)
@Synchronized fun allocate(capacity: Int): ByteBuffer {
val targetSize = getTargetSize(capacity)
val pool = pools[targetSize] ?: throw IllegalStateException("No pool for size $targetSize available")
if (pool.isEmpty()) {
LOG.trace("Creating new buffer of size $targetSize")
return ByteBuffer.allocate(targetSize)
} else {
return pool.pop()
}
}
/**
* Returns a buffer that has the size of the Bitmessage network message header, 24 bytes.
* @return a buffer of size 24
*/
@Synchronized fun allocateHeaderBuffer(): ByteBuffer {
val pool = pools[HEADER_SIZE]
if (pool == null || pool.isEmpty()) {
return ByteBuffer.allocate(HEADER_SIZE)
} else {
return pool.pop()
}
}
@Synchronized fun deallocate(buffer: ByteBuffer) {
buffer.clear()
val pool = pools[buffer.capacity()] ?: throw IllegalArgumentException("Illegal buffer capacity ${buffer.capacity()} one of ${pools.keys} expected.")
pool.push(buffer)
}
private fun getTargetSize(capacity: Int): Int {
for (size in pools.keys) {
if (size >= capacity) return size
}
throw IllegalArgumentException("Requested capacity too large: requested=$capacity; max=$MAX_PAYLOAD_SIZE")
}
}

View File

@ -54,9 +54,8 @@ object ExtendedEncodingFactory {
fun unzip(zippedData: ByteArray): ExtendedEncoding? { fun unzip(zippedData: ByteArray): ExtendedEncoding? {
try { try {
InflaterInputStream(ByteArrayInputStream(zippedData)).use { unzipper -> InflaterInputStream(ByteArrayInputStream(zippedData)).use { unzipper ->
val reader = Reader.getInstance()
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
val map = reader.read(unzipper) as MPMap<MPString, MPType<*>> val map = Reader.read(unzipper) as MPMap<MPString, MPType<*>>
val messageType = map[KEY_MESSAGE_TYPE] val messageType = map[KEY_MESSAGE_TYPE]
if (messageType == null) { if (messageType == null) {
LOG.error("Missing message type") LOG.error("Missing message type")

View File

@ -156,11 +156,11 @@ object Factory {
return GetPubkey.read(stream, streamNumber, length, version) return GetPubkey.read(stream, streamNumber, length, version)
} }
@JvmStatic fun readPubkey(version: Long, stream: Long, `is`: InputStream, length: Int, encrypted: Boolean): Pubkey? { @JvmStatic fun readPubkey(version: Long, stream: Long, input: InputStream, length: Int, encrypted: Boolean): Pubkey? {
when (version.toInt()) { when (version.toInt()) {
2 -> return V2Pubkey.read(`is`, stream) 2 -> return V2Pubkey.read(input, stream)
3 -> return V3Pubkey.read(`is`, stream) 3 -> return V3Pubkey.read(input, stream)
4 -> return V4Pubkey.read(`is`, stream, length, encrypted) 4 -> return V4Pubkey.read(input, stream, length, encrypted)
} }
LOG.debug("Unexpected pubkey version $version, handling as generic payload object") LOG.debug("Unexpected pubkey version $version, handling as generic payload object")
return null return null

View File

@ -39,130 +39,88 @@ object V3MessageFactory {
private val LOG = LoggerFactory.getLogger(V3MessageFactory::class.java) private val LOG = LoggerFactory.getLogger(V3MessageFactory::class.java)
@JvmStatic @JvmStatic
fun read(`in`: InputStream): NetworkMessage? { fun read(input: InputStream): NetworkMessage? {
findMagic(`in`) findMagic(input)
val command = getCommand(`in`) val command = getCommand(input)
val length = Decode.uint32(`in`).toInt() val length = Decode.uint32(input).toInt()
if (length > 1600003) { if (length > 1600003) {
throw NodeException("Payload of $length bytes received, no more than 1600003 was expected.") throw NodeException("Payload of $length bytes received, no more than 1600003 was expected.")
} }
val checksum = Decode.bytes(`in`, 4) val checksum = Decode.bytes(input, 4)
val payloadBytes = Decode.bytes(`in`, length) val payloadBytes = Decode.bytes(input, length)
if (testChecksum(checksum, payloadBytes)) { if (testChecksum(checksum, payloadBytes)) {
val payload = getPayload(command, ByteArrayInputStream(payloadBytes), length) val payload = getPayload(command, ByteArrayInputStream(payloadBytes), length)
if (payload != null) return payload?.let { NetworkMessage(payload) }
return NetworkMessage(payload)
else
return null
} else { } else {
throw IOException("Checksum failed for message '$command'") throw IOException("Checksum failed for message '$command'")
} }
} }
@JvmStatic @JvmStatic
fun getPayload(command: String, stream: InputStream, length: Int): MessagePayload? { fun getPayload(command: String, stream: InputStream, length: Int): MessagePayload? = when (command) {
when (command) { "version" -> parseVersion(stream)
"version" -> return parseVersion(stream) "verack" -> VerAck()
"verack" -> return VerAck() "addr" -> Addr(parseList(stream) { parseAddress(it, false) })
"addr" -> return parseAddr(stream) "inv" -> Inv(parseList(stream) { parseInventoryVector(it) })
"inv" -> return parseInv(stream) "getdata" -> GetData(parseList(stream) { parseInventoryVector(it) })
"getdata" -> return parseGetData(stream) "object" -> readObject(stream, length)
"object" -> return readObject(stream, length) "custom" -> readCustom(stream, length)
"custom" -> return readCustom(stream, length)
else -> { else -> {
LOG.debug("Unknown command: " + command) LOG.debug("Unknown command: $command")
return null null
}
} }
} }
private fun readCustom(`in`: InputStream, length: Int): MessagePayload { private fun readCustom(input: InputStream, length: Int): MessagePayload = CustomMessage.read(input, length)
return CustomMessage.read(`in`, length)
}
@JvmStatic @JvmStatic
fun readObject(`in`: InputStream, length: Int): ObjectMessage { fun readObject(input: InputStream, length: Int): ObjectMessage {
val counter = AccessCounter() val counter = AccessCounter()
val nonce = Decode.bytes(`in`, 8, counter) val nonce = Decode.bytes(input, 8, counter)
val expiresTime = Decode.int64(`in`, counter) val expiresTime = Decode.int64(input, counter)
val objectType = Decode.uint32(`in`, counter) val objectType = Decode.uint32(input, counter)
val version = Decode.varInt(`in`, counter) val version = Decode.varInt(input, counter)
val stream = Decode.varInt(`in`, counter) val stream = Decode.varInt(input, counter)
val data = Decode.bytes(`in`, length - counter.length()) val data = Decode.bytes(input, length - counter.length())
var payload: ObjectPayload val payload: ObjectPayload = try {
try { Factory.getObjectPayload(objectType, version, stream, ByteArrayInputStream(data), data.size)
val dataStream = ByteArrayInputStream(data)
payload = Factory.getObjectPayload(objectType, version, stream, dataStream, data.size)
} catch (e: Exception) { } catch (e: Exception) {
if (LOG.isTraceEnabled) { if (LOG.isTraceEnabled) {
LOG.trace("Could not parse object payload - using generic payload instead", e) LOG.trace("Could not parse object payload - using generic payload instead", e)
LOG.trace(Strings.hex(data)) LOG.trace(Strings.hex(data))
} }
payload = GenericPayload(version, stream, data) GenericPayload(version, stream, data)
} }
return ObjectMessage.Builder() return ObjectMessage(
.nonce(nonce) nonce, expiresTime, payload, objectType, version, stream
.expiresTime(expiresTime) )
.objectType(objectType)
.stream(stream)
.payload(payload)
.build()
} }
private fun parseGetData(stream: InputStream): GetData { private fun <T> parseList(stream: InputStream, reader: (InputStream) -> (T)): List<T> {
val count = Decode.varInt(stream) val count = Decode.varInt(stream)
val inventoryVectors = LinkedList<InventoryVector>() val items = LinkedList<T>()
for (i in 0..count - 1) { for (i in 0 until count) {
inventoryVectors.add(parseInventoryVector(stream)) items.add(reader(stream))
} }
return GetData(inventoryVectors) return items
} }
private fun parseInv(stream: InputStream): Inv { private fun parseVersion(stream: InputStream) = Version(
val count = Decode.varInt(stream) version = Decode.int32(stream),
val inventoryVectors = LinkedList<InventoryVector>() services = Decode.int64(stream),
for (i in 0..count - 1) { timestamp = Decode.int64(stream),
inventoryVectors.add(parseInventoryVector(stream)) addrRecv = parseAddress(stream, true),
} addrFrom = parseAddress(stream, true),
return Inv(inventoryVectors) nonce = Decode.int64(stream),
} userAgent = Decode.varString(stream),
streams = Decode.varIntList(stream)
)
private fun parseAddr(stream: InputStream): Addr { private fun parseInventoryVector(stream: InputStream) = InventoryVector(Decode.bytes(stream, 32))
val count = Decode.varInt(stream)
val networkAddresses = LinkedList<NetworkAddress>()
for (i in 0..count - 1) {
networkAddresses.add(parseAddress(stream, false))
}
return Addr(networkAddresses)
}
private fun parseVersion(stream: InputStream): Version {
val version = Decode.int32(stream)
val services = Decode.int64(stream)
val timestamp = Decode.int64(stream)
val addrRecv = parseAddress(stream, true)
val addrFrom = parseAddress(stream, true)
val nonce = Decode.int64(stream)
val userAgent = Decode.varString(stream)
val streamNumbers = Decode.varIntList(stream)
return Version.Builder()
.version(version)
.services(services)
.timestamp(timestamp)
.addrRecv(addrRecv).addrFrom(addrFrom)
.nonce(nonce)
.userAgent(userAgent)
.streams(*streamNumbers).build()
}
private fun parseInventoryVector(stream: InputStream): InventoryVector {
return InventoryVector(Decode.bytes(stream, 32))
}
private fun parseAddress(stream: InputStream, light: Boolean): NetworkAddress { private fun parseAddress(stream: InputStream, light: Boolean): NetworkAddress {
val time: Long val time: Long
@ -177,23 +135,15 @@ object V3MessageFactory {
val services = Decode.int64(stream) val services = Decode.int64(stream)
val ipv6 = Decode.bytes(stream, 16) val ipv6 = Decode.bytes(stream, 16)
val port = Decode.uint16(stream) val port = Decode.uint16(stream)
return NetworkAddress.Builder()
.time(time) return NetworkAddress(
.stream(streamNumber) time, streamNumber, services, ipv6, port
.services(services) )
.ipv6(ipv6)
.port(port)
.build()
} }
private fun testChecksum(checksum: ByteArray, payload: ByteArray): Boolean { private fun testChecksum(checksum: ByteArray, payload: ByteArray): Boolean {
val payloadChecksum = cryptography().sha512(payload) val payloadChecksum = cryptography().sha512(payload)
for (i in checksum.indices) { return checksum.indices.none { checksum[it] != payloadChecksum[it] }
if (checksum[i] != payloadChecksum[i]) {
return false
}
}
return true
} }
private fun getCommand(stream: InputStream): String { private fun getCommand(stream: InputStream): String {
@ -210,10 +160,10 @@ object V3MessageFactory {
return String(bytes, 0, end, Charsets.US_ASCII) return String(bytes, 0, end, Charsets.US_ASCII)
} }
private fun findMagic(`in`: InputStream) { private fun findMagic(input: InputStream) {
var pos = 0 var pos = 0
for (i in 0..1619999) { for (i in 0..1619999) {
val b = `in`.read().toByte() val b = input.read().toByte()
if (b == NetworkMessage.MAGIC_BYTES[pos]) { if (b == NetworkMessage.MAGIC_BYTES[pos]) {
if (pos + 1 == NetworkMessage.MAGIC_BYTES.size) { if (pos + 1 == NetworkMessage.MAGIC_BYTES.size) {
return return

View File

@ -30,8 +30,7 @@ import java.util.*
* Similar to the [V3MessageFactory], but used for NIO buffers which may or may not contain a whole message. * Similar to the [V3MessageFactory], but used for NIO buffers which may or may not contain a whole message.
*/ */
class V3MessageReader { class V3MessageReader {
private var headerBuffer: ByteBuffer? = null val buffer: ByteBuffer = ByteBuffer.allocate(MAX_PAYLOAD_SIZE)
private var dataBuffer: ByteBuffer? = null
private var state: ReaderState? = ReaderState.MAGIC private var state: ReaderState? = ReaderState.MAGIC
private var command: String? = null private var command: String? = null
@ -40,89 +39,83 @@ class V3MessageReader {
private val messages = LinkedList<NetworkMessage>() private val messages = LinkedList<NetworkMessage>()
fun getActiveBuffer(): ByteBuffer {
if (state != null && state != ReaderState.DATA) {
if (headerBuffer == null) {
headerBuffer = BufferPool.allocateHeaderBuffer()
}
}
return if (state == ReaderState.DATA)
dataBuffer ?: throw IllegalStateException("data buffer is null")
else
headerBuffer ?: throw IllegalStateException("header buffer is null")
}
fun update() { fun update() {
if (state != ReaderState.DATA) { if (state != ReaderState.DATA) {
getActiveBuffer() // in order to initialize buffer.flip()
headerBuffer?.flip() ?: throw IllegalStateException("header buffer is null") }
var s = when (state) {
ReaderState.MAGIC -> magic()
ReaderState.HEADER -> header()
ReaderState.DATA -> data()
else -> ReaderState.WAIT_FOR_DATA
}
while (s != ReaderState.WAIT_FOR_DATA) {
s = when (state) {
ReaderState.MAGIC -> magic()
ReaderState.HEADER -> header()
ReaderState.DATA -> data(flip = false)
else -> ReaderState.WAIT_FOR_DATA
} }
when (state) {
V3MessageReader.ReaderState.MAGIC -> magic(headerBuffer ?: throw IllegalStateException("header buffer is null"))
V3MessageReader.ReaderState.HEADER -> header(headerBuffer ?: throw IllegalStateException("header buffer is null"))
V3MessageReader.ReaderState.DATA -> data(dataBuffer ?: throw IllegalStateException("data buffer is null"))
} }
} }
private fun magic(headerBuffer: ByteBuffer) { private fun magic(): ReaderState = if (!findMagicBytes(buffer)) {
if (!findMagicBytes(headerBuffer)) { buffer.compact()
headerBuffer.compact() ReaderState.WAIT_FOR_DATA
return
} else { } else {
state = ReaderState.HEADER state = ReaderState.HEADER
header(headerBuffer) ReaderState.HEADER
}
} }
private fun header(headerBuffer: ByteBuffer) { private fun header(): ReaderState {
if (headerBuffer.remaining() < 20) { if (buffer.remaining() < 20) {
headerBuffer.compact() buffer.compact()
headerBuffer.limit(20) return ReaderState.WAIT_FOR_DATA
return
} }
command = getCommand(headerBuffer) command = getCommand(buffer)
length = Decode.uint32(headerBuffer).toInt() length = Decode.uint32(buffer).toInt()
if (length > MAX_PAYLOAD_SIZE) { if (length > MAX_PAYLOAD_SIZE) {
throw NodeException("Payload of " + length + " bytes received, no more than " + throw NodeException(
MAX_PAYLOAD_SIZE + " was expected.") "Payload of " + length + " bytes received, no more than " +
MAX_PAYLOAD_SIZE + " was expected."
)
} }
headerBuffer.get(checksum) buffer.get(checksum)
state = ReaderState.DATA state = ReaderState.DATA
this.headerBuffer = null return ReaderState.DATA
BufferPool.deallocate(headerBuffer)
val dataBuffer = BufferPool.allocate(length)
this.dataBuffer = dataBuffer
dataBuffer.clear()
dataBuffer.limit(length)
data(dataBuffer)
} }
private fun data(dataBuffer: ByteBuffer) { private fun data(flip: Boolean = true): ReaderState {
if (dataBuffer.position() < length) { if (flip) {
return if (buffer.position() < length) {
return ReaderState.WAIT_FOR_DATA
} else { } else {
dataBuffer.flip() buffer.flip()
} }
if (!testChecksum(dataBuffer)) { } else if (buffer.remaining() < length) {
buffer.compact()
return ReaderState.WAIT_FOR_DATA
}
if (!testChecksum(buffer)) {
state = ReaderState.MAGIC state = ReaderState.MAGIC
this.dataBuffer = null buffer.clear()
BufferPool.deallocate(dataBuffer)
throw NodeException("Checksum failed for message '$command'") throw NodeException("Checksum failed for message '$command'")
} }
try { try {
V3MessageFactory.getPayload( V3MessageFactory.getPayload(
command ?: throw IllegalStateException("command is null"), command ?: throw IllegalStateException("command is null"),
ByteArrayInputStream(dataBuffer.array(), ByteArrayInputStream(
dataBuffer.arrayOffset() + dataBuffer.position(), length), buffer.array(),
buffer.arrayOffset() + buffer.position(), length
),
length length
)?.let { messages.add(NetworkMessage(it)) } )?.let { messages.add(NetworkMessage(it)) }
} catch (e: IOException) { } catch (e: IOException) {
throw NodeException(e.message) throw NodeException(e.message)
} finally { } finally {
state = ReaderState.MAGIC state = ReaderState.MAGIC
this.dataBuffer = null
BufferPool.deallocate(dataBuffer)
} }
return ReaderState.MAGIC
} }
fun getMessages(): MutableList<NetworkMessage> { fun getMessages(): MutableList<NetworkMessage> {
@ -163,8 +156,10 @@ class V3MessageReader {
} }
private fun testChecksum(buffer: ByteBuffer): Boolean { private fun testChecksum(buffer: ByteBuffer): Boolean {
val payloadChecksum = cryptography().sha512(buffer.array(), val payloadChecksum = cryptography().sha512(
buffer.arrayOffset() + buffer.position(), length) buffer.array(),
buffer.arrayOffset() + buffer.position(), length
)
for (i in checksum.indices) { for (i in checksum.indices) {
if (checksum[i] != payloadChecksum[i]) { if (checksum[i] != payloadChecksum[i]) {
return false return false
@ -173,17 +168,7 @@ class V3MessageReader {
return true return true
} }
/**
* De-allocates all buffers. This method should be called iff the reader isn't used anymore, i.e. when its
* connection is severed.
*/
fun cleanup() {
state = null
headerBuffer?.let { BufferPool.deallocate(it) }
dataBuffer?.let { BufferPool.deallocate(it) }
}
private enum class ReaderState { private enum class ReaderState {
MAGIC, HEADER, DATA MAGIC, HEADER, DATA, WAIT_FOR_DATA
} }
} }

View File

@ -36,12 +36,16 @@ 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, InternalContext.ContextHolder { abstract class AbstractCryptography protected constructor(@JvmField protected val provider: Provider) : Cryptography,
InternalContext.ContextHolder {
private lateinit var ctx: InternalContext private lateinit var ctx: InternalContext
@JvmField protected val ALGORITHM_ECDSA = "ECDSA" @JvmField
@JvmField protected val ALGORITHM_ECDSA_SHA1 = "SHA1withECDSA" protected val ALGORITHM_ECDSA = "ECDSA"
@JvmField protected val ALGORITHM_EVP_SHA256 = "SHA256withECDSA" @JvmField
protected val ALGORITHM_ECDSA_SHA1 = "SHA1withECDSA"
@JvmField
protected val ALGORITHM_EVP_SHA256 = "SHA256withECDSA"
override fun setContext(context: InternalContext) { override fun setContext(context: InternalContext) {
ctx = context ctx = context
@ -65,8 +69,12 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va
return mda.digest(mda.digest()) return mda.digest(mda.digest())
} }
override fun doubleSha512(data: ByteArray, length: Int): ByteArray { override fun doubleSha512(data: ByteArray, length: Int) = doubleHash("SHA-512", data, length);
val mda = md("SHA-512")
override fun doubleSha256(data: ByteArray, length: Int) = doubleHash("SHA-256", data, length);
private fun doubleHash(method: String, data: ByteArray, length: Int): ByteArray {
val mda = md(method)
mda.update(data, 0, length) mda.update(data, 0, length)
return mda.digest(mda.digest()) return mda.digest(mda.digest())
} }
@ -75,12 +83,6 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va
return hash("RIPEMD160", *data) return hash("RIPEMD160", *data)
} }
override fun doubleSha256(data: ByteArray, length: Int): ByteArray {
val mda = md("SHA-256")
mda.update(data, 0, length)
return mda.digest(mda.digest())
}
override fun sha1(vararg data: ByteArray): ByteArray { override fun sha1(vararg data: ByteArray): ByteArray {
return hash("SHA-1", *data) return hash("SHA-1", *data)
} }
@ -91,13 +93,17 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va
return result return result
} }
override fun doProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, override fun doProofOfWork(
extraBytes: Long, callback: ProofOfWorkEngine.Callback) { objectMessage: ObjectMessage, nonceTrialsPerByte: Long,
extraBytes: Long, callback: ProofOfWorkEngine.Callback
) {
val initialHash = getInitialHash(objectMessage) val initialHash = getInitialHash(objectMessage)
val target = getProofOfWorkTarget(objectMessage, val target = getProofOfWorkTarget(
max(nonceTrialsPerByte, NETWORK_NONCE_TRIALS_PER_BYTE), max(extraBytes, NETWORK_EXTRA_BYTES)) objectMessage,
max(nonceTrialsPerByte, NETWORK_NONCE_TRIALS_PER_BYTE), max(extraBytes, NETWORK_EXTRA_BYTES)
)
ctx.proofOfWorkEngine.calculateNonce(initialHash, target, callback) ctx.proofOfWorkEngine.calculateNonce(initialHash, target, callback)
} }
@ -105,7 +111,10 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va
@Throws(InsufficientProofOfWorkException::class) @Throws(InsufficientProofOfWorkException::class)
override fun checkProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) { override fun checkProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) {
val target = getProofOfWorkTarget(objectMessage, nonceTrialsPerByte, extraBytes) val target = getProofOfWorkTarget(objectMessage, nonceTrialsPerByte, extraBytes)
val value = doubleSha512(objectMessage.nonce ?: throw ApplicationException("Object without nonce"), getInitialHash(objectMessage)) 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)
} }
@ -136,7 +145,11 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va
return sha512(objectMessage.payloadBytesWithoutNonce) return sha512(objectMessage.payloadBytesWithoutNonce)
} }
override fun getProofOfWorkTarget(objectMessage: 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")
@ -181,12 +194,16 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va
} }
override fun createPubkey(version: Long, stream: Long, privateSigningKey: ByteArray, privateEncryptionKey: ByteArray, override fun createPubkey(
nonceTrialsPerByte: Long, extraBytes: Long, vararg features: Pubkey.Feature): Pubkey { version: Long, stream: Long, privateSigningKey: ByteArray, privateEncryptionKey: ByteArray,
return Factory.createPubkey(version, stream, nonceTrialsPerByte: Long, extraBytes: Long, vararg features: Pubkey.Feature
): Pubkey {
return Factory.createPubkey(
version, stream,
createPublicKey(privateSigningKey), createPublicKey(privateSigningKey),
createPublicKey(privateEncryptionKey), createPublicKey(privateEncryptionKey),
nonceTrialsPerByte, extraBytes, *features) nonceTrialsPerByte, extraBytes, *features
)
} }
override fun keyToBigInt(privateKey: ByteArray): BigInteger { override fun keyToBigInt(privateKey: ByteArray): BigInteger {

View File

@ -0,0 +1,33 @@
/*
* 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.
*/
package ch.dissem.bitmessage.ports
import ch.dissem.bitmessage.entity.valueobject.Label
import ch.dissem.bitmessage.utils.SqlStrings.join
abstract class AbstractLabelRepository : LabelRepository {
override fun getLabels(): List<Label> {
return find("1=1")
}
override fun getLabels(vararg types: Label.Type): List<Label> {
return find("type IN (${join(*types)})")
}
protected abstract fun find(where: String): List<Label>
}

View File

@ -21,8 +21,7 @@ import ch.dissem.bitmessage.entity.BitmessageAddress
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.entity.valueobject.Label import ch.dissem.bitmessage.entity.valueobject.Label
import ch.dissem.bitmessage.exception.ApplicationException import ch.dissem.bitmessage.utils.Collections.single
import ch.dissem.bitmessage.utils.SqlStrings.join
import ch.dissem.bitmessage.utils.Strings import ch.dissem.bitmessage.utils.Strings
import ch.dissem.bitmessage.utils.UnixTime import ch.dissem.bitmessage.utils.UnixTime
import java.util.* import java.util.*
@ -110,27 +109,8 @@ abstract class AbstractMessageRepository : MessageRepository, InternalContext.Co
return find("iv IN (SELECT child FROM Message_Parent WHERE parent=X'${Strings.hex(parent.inventoryVector!!.hash)}')") return find("iv IN (SELECT child FROM Message_Parent WHERE parent=X'${Strings.hex(parent.inventoryVector!!.hash)}')")
} }
override fun getConversation(conversationId: UUID): List<Plaintext> { override fun getConversation(conversationId: UUID, offset: Int, limit: Int): List<Plaintext> {
return find("conversation=X'${conversationId.toString().replace("-", "")}'") return find("conversation=X'${conversationId.toString().replace("-", "")}'", offset, limit)
}
override fun getLabels(): List<Label> {
return findLabels("1=1")
}
override fun getLabels(vararg types: Label.Type): List<Label> {
return findLabels("type IN (${join(*types)})")
}
protected abstract fun findLabels(where: String): List<Label>
protected fun <T> single(collection: Collection<T>): T? {
return when (collection.size) {
0 -> null
1 -> collection.iterator().next()
else -> throw ApplicationException("This shouldn't happen, found ${collection.size} items, one or none was expected")
}
} }
/** /**

View File

@ -156,6 +156,16 @@ interface Cryptography {
fun doProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, fun doProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long,
extraBytes: Long, callback: ProofOfWorkEngine.Callback) extraBytes: Long, callback: ProofOfWorkEngine.Callback)
@JvmSynthetic
fun doProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long,
extraBytes: Long, callback: (ByteArray, ByteArray) -> Unit) {
doProofOfWork(objectMessage, nonceTrialsPerByte, extraBytes, object : ProofOfWorkEngine.Callback {
override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) {
callback.invoke(initialHash, nonce)
}
})
}
/** /**
* @param objectMessage to be checked * @param objectMessage to be checked
* * * *

View File

@ -35,9 +35,9 @@ open class DefaultLabeler : Labeler, InternalContext.ContextHolder {
msg.status = RECEIVED msg.status = RECEIVED
val labelsToAdd = val labelsToAdd =
if (msg.type == BROADCAST) { if (msg.type == BROADCAST) {
ctx.messageRepository.getLabels(Label.Type.BROADCAST, Label.Type.UNREAD) ctx.labelRepository.getLabels(Label.Type.BROADCAST, Label.Type.UNREAD)
} else { } else {
ctx.messageRepository.getLabels(Label.Type.INBOX, Label.Type.UNREAD) ctx.labelRepository.getLabels(Label.Type.INBOX, Label.Type.UNREAD)
} }
msg.addLabels(labelsToAdd) msg.addLabels(labelsToAdd)
listener?.invoke(msg, labelsToAdd, emptyList()) listener?.invoke(msg, labelsToAdd, emptyList())
@ -45,7 +45,7 @@ open class DefaultLabeler : Labeler, InternalContext.ContextHolder {
override fun markAsDraft(msg: Plaintext) { override fun markAsDraft(msg: Plaintext) {
msg.status = DRAFT msg.status = DRAFT
val labelsToAdd = ctx.messageRepository.getLabels(Label.Type.DRAFT) val labelsToAdd = ctx.labelRepository.getLabels(Label.Type.DRAFT)
msg.addLabels(labelsToAdd) msg.addLabels(labelsToAdd)
listener?.invoke(msg, labelsToAdd, emptyList()) listener?.invoke(msg, labelsToAdd, emptyList())
} }
@ -58,7 +58,7 @@ open class DefaultLabeler : Labeler, InternalContext.ContextHolder {
} }
val labelsToRemove = msg.labels.filter { it.type == Label.Type.DRAFT } val labelsToRemove = msg.labels.filter { it.type == Label.Type.DRAFT }
msg.removeLabel(Label.Type.DRAFT) msg.removeLabel(Label.Type.DRAFT)
val labelsToAdd = ctx.messageRepository.getLabels(Label.Type.OUTBOX) val labelsToAdd = ctx.labelRepository.getLabels(Label.Type.OUTBOX)
msg.addLabels(labelsToAdd) msg.addLabels(labelsToAdd)
listener?.invoke(msg, labelsToAdd, labelsToRemove) listener?.invoke(msg, labelsToAdd, labelsToRemove)
} }
@ -67,7 +67,7 @@ open class DefaultLabeler : Labeler, InternalContext.ContextHolder {
msg.status = SENT msg.status = SENT
val labelsToRemove = msg.labels.filter { it.type == Label.Type.OUTBOX } val labelsToRemove = msg.labels.filter { it.type == Label.Type.OUTBOX }
msg.removeLabel(Label.Type.OUTBOX) msg.removeLabel(Label.Type.OUTBOX)
val labelsToAdd = ctx.messageRepository.getLabels(Label.Type.SENT) val labelsToAdd = ctx.labelRepository.getLabels(Label.Type.SENT)
msg.addLabels(labelsToAdd) msg.addLabels(labelsToAdd)
listener?.invoke(msg, labelsToAdd, labelsToRemove) listener?.invoke(msg, labelsToAdd, labelsToRemove)
} }
@ -83,7 +83,7 @@ open class DefaultLabeler : Labeler, InternalContext.ContextHolder {
} }
override fun markAsUnread(msg: Plaintext) { override fun markAsUnread(msg: Plaintext) {
val labelsToAdd = ctx.messageRepository.getLabels(Label.Type.UNREAD) val labelsToAdd = ctx.labelRepository.getLabels(Label.Type.UNREAD)
msg.addLabels(labelsToAdd) msg.addLabels(labelsToAdd)
listener?.invoke(msg, labelsToAdd, emptyList()) listener?.invoke(msg, labelsToAdd, emptyList())
} }
@ -91,7 +91,7 @@ open class DefaultLabeler : Labeler, InternalContext.ContextHolder {
override fun delete(msg: Plaintext) { override fun delete(msg: Plaintext) {
val labelsToRemove = msg.labels.toSet() val labelsToRemove = msg.labels.toSet()
msg.labels.clear() msg.labels.clear()
val labelsToAdd = ctx.messageRepository.getLabels(Label.Type.TRASH) val labelsToAdd = ctx.labelRepository.getLabels(Label.Type.TRASH)
msg.addLabels(labelsToAdd) msg.addLabels(labelsToAdd)
listener?.invoke(msg, labelsToAdd, labelsToRemove) listener?.invoke(msg, labelsToAdd, labelsToRemove)
} }

View File

@ -0,0 +1,27 @@
/*
* 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.
*/
package ch.dissem.bitmessage.ports
import ch.dissem.bitmessage.entity.valueobject.Label
interface LabelRepository {
fun getLabels(): List<Label>
fun getLabels(vararg types: Label.Type): List<Label>
fun save(label: Label)
}

View File

@ -24,12 +24,6 @@ import ch.dissem.bitmessage.entity.valueobject.Label
import java.util.* import java.util.*
interface MessageRepository { interface MessageRepository {
fun getLabels(): List<Label>
fun getLabels(vararg types: Label.Type): List<Label>
fun save(label: Label)
fun countUnread(label: Label?): Int fun countUnread(label: Label?): Int
fun getAllMessages(): List<Plaintext> fun getAllMessages(): List<Plaintext>
@ -47,7 +41,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?, offset: Int = 0, limit: Int = 0): List<UUID>
fun findMessages(label: Label?): List<Plaintext> fun findMessages(label: Label?): List<Plaintext>
@ -74,5 +68,5 @@ interface MessageRepository {
* * * *
* @return all messages with the given conversation ID * @return all messages with the given conversation ID
*/ */
fun getConversation(conversationId: UUID): Collection<Plaintext> fun getConversation(conversationId: UUID, offset: Int = 0, limit: Int = 0): Collection<Plaintext>
} }

View File

@ -31,4 +31,13 @@ interface NodeRegistry {
fun getKnownAddresses(limit: Int, vararg streams: Long): List<NetworkAddress> fun getKnownAddresses(limit: Int, vararg streams: Long): List<NetworkAddress>
fun offerAddresses(nodes: List<NetworkAddress>) fun offerAddresses(nodes: List<NetworkAddress>)
fun update(node: NetworkAddress)
fun remove(node: NetworkAddress)
/**
* Remove stale nodes
*/
fun cleanup()
} }

View File

@ -31,8 +31,8 @@ object NodeRegistryHelper {
@JvmStatic @JvmStatic
fun loadStableNodes(): Map<Long, Set<NetworkAddress>> { fun loadStableNodes(): Map<Long, Set<NetworkAddress>> {
javaClass.classLoader.getResourceAsStream("nodes.txt").use { `in` -> javaClass.classLoader.getResourceAsStream("nodes.txt").use { input ->
val scanner = Scanner(`in`) val scanner = Scanner(input)
var stream: Long = 0 var stream: Long = 0
val result = HashMap<Long, Set<NetworkAddress>>() val result = HashMap<Long, Set<NetworkAddress>>()
var streamSet: MutableSet<NetworkAddress>? = null var streamSet: MutableSet<NetworkAddress>? = null
@ -41,7 +41,7 @@ object NodeRegistryHelper {
val line = scanner.nextLine().trim { it <= ' ' } val line = scanner.nextLine().trim { it <= ' ' }
if (line.startsWith("[stream")) { if (line.startsWith("[stream")) {
stream = java.lang.Long.parseLong(line.substring(8, line.lastIndexOf(']'))) stream = java.lang.Long.parseLong(line.substring(8, line.lastIndexOf(']')))
streamSet = HashSet<NetworkAddress>() streamSet = HashSet()
result.put(stream, streamSet) result.put(stream, streamSet)
} else if (streamSet != null && !line.isEmpty() && !line.startsWith("#")) { } else if (streamSet != null && !line.isEmpty() && !line.startsWith("#")) {
val portIndex = line.lastIndexOf(':') val portIndex = line.lastIndexOf(':')

View File

@ -23,16 +23,32 @@ interface ProofOfWorkEngine {
/** /**
* Returns a nonce, such that the first 8 bytes from sha512(sha512(nonce||initialHash)) represent a unsigned long * Returns a nonce, such that the first 8 bytes from sha512(sha512(nonce||initialHash)) represent a unsigned long
* smaller than target. * smaller than target.
*
* @param initialHash the SHA-512 hash of the object to send, sans nonce * @param initialHash the SHA-512 hash of the object to send, sans nonce
* *
* @param target the target, representing an unsigned long * @param target the target, representing an unsigned long
* * * @param callback called with the initial hash and the calculated nonce as argument. The ProofOfWorkEngine
* @param callback called with the calculated nonce as argument. The ProofOfWorkEngine implementation must make * implementation must make sure this is only called once.
* * sure this is only called once.
*/ */
fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: Callback) fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: Callback)
/**
* Returns a nonce, such that the first 8 bytes from sha512(sha512(nonce||initialHash)) represent a unsigned long
* smaller than target.
*
* @param initialHash the SHA-512 hash of the object to send, sans nonce
* @param target the target, representing an unsigned long
* @param callback called with the initial hash and the calculated nonce as argument. The ProofOfWorkEngine
* implementation must make sure this is only called once.
*/
@JvmSynthetic
fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: (ByteArray, ByteArray) -> Unit) {
calculateNonce(initialHash, target, object : Callback {
override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) {
callback.invoke(initialHash, nonce)
}
})
}
interface Callback { interface Callback {
/** /**
* @param nonce 8 bytes nonce * @param nonce 8 bytes nonce

View File

@ -641,24 +641,26 @@ private class Encoder(val options: Options, output: ByteArray) : Coder(output) {
* Lookup table for turning Base64 alphabet positions (6 bits) * Lookup table for turning Base64 alphabet positions (6 bits)
* into output bytes. * into output bytes.
*/ */
private val ENCODE = charArrayOf( private val ENCODE = charsAsBytes(
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
).map { it.toByte() }.toByteArray() )
/** /**
* Lookup table for turning Base64 alphabet positions (6 bits) * Lookup table for turning Base64 alphabet positions (6 bits)
* into output bytes. * into output bytes.
*/ */
private val ENCODE_WEBSAFE = charArrayOf( private val ENCODE_WEBSAFE = charsAsBytes(
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'
).map { it.toByte() }.toByteArray() )
private fun charsAsBytes(vararg elements: Char): ByteArray = elements.map { it.toByte() }.toByteArray()
} }
} }

View File

@ -16,6 +16,7 @@
package ch.dissem.bitmessage.utils package ch.dissem.bitmessage.utils
import ch.dissem.bitmessage.exception.ApplicationException
import java.util.* import java.util.*
object Collections { object Collections {
@ -67,4 +68,12 @@ object Collections {
} }
throw IllegalArgumentException("Empty collection? Size: " + collection.size) throw IllegalArgumentException("Empty collection? Size: " + collection.size)
} }
@JvmStatic fun <T> single(collection: Collection<T>): T? {
return when (collection.size) {
0 -> null
1 -> collection.iterator().next()
else -> throw ApplicationException("This shouldn't happen, found ${collection.size} items, one or none was expected")
}
}
} }

View File

@ -16,10 +16,11 @@
package ch.dissem.bitmessage.utils package ch.dissem.bitmessage.utils
import ch.dissem.bitmessage.entity.Conversation
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.entity.valueobject.Label
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
@ -30,7 +31,11 @@ import java.util.regex.Pattern.CASE_INSENSITIVE
*/ */
class ConversationService(private val messageRepository: MessageRepository) { class ConversationService(private val messageRepository: MessageRepository) {
private val SUBJECT_PREFIX = Pattern.compile("^(re|fwd?):", CASE_INSENSITIVE) private val SUBJECT_PREFIX = Pattern.compile("^(re|fwd?):\\s*", CASE_INSENSITIVE)
fun findConversations(label: Label?, offset: Int = 0, limit: Int = 0, conversationLimit: Int = 10) =
messageRepository.findConversations(label, offset, limit)
.map { getConversation(it, conversationLimit) }
/** /**
* Retrieve the whole conversation from one single message. If the message isn't part * Retrieve the whole conversation from one single message. If the message isn't part
@ -41,7 +46,7 @@ class ConversationService(private val messageRepository: MessageRepository) {
* * * *
* @return a list of messages that belong to the same conversation. * @return a list of messages that belong to the same conversation.
*/ */
fun getConversation(message: Plaintext): List<Plaintext> { fun getConversation(message: Plaintext): Conversation {
return getConversation(message.conversationId) return getConversation(message.conversationId)
} }
@ -58,8 +63,8 @@ class ConversationService(private val messageRepository: MessageRepository) {
return result return result
} }
fun getConversation(conversationId: UUID): List<Plaintext> { fun getConversation(conversationId: UUID, limit: Int = 0): Conversation {
val messages = sorted(messageRepository.getConversation(conversationId)) val messages = sorted(messageRepository.getConversation(conversationId, 0, limit))
val map = HashMap<InventoryVector, Plaintext>(messages.size) val map = HashMap<InventoryVector, Plaintext>(messages.size)
for (message in messages) { for (message in messages) {
message.inventoryVector?.let { message.inventoryVector?.let {
@ -74,7 +79,7 @@ class ConversationService(private val messageRepository: MessageRepository) {
result.add(pos, last) result.add(pos, last)
addAncestors(last, result, messages, map) addAncestors(last, result, messages, map)
} }
return result return Conversation(conversationId, getSubject(result) ?: "", result)
} }
fun getSubject(conversation: List<Plaintext>): String? { fun getSubject(conversation: List<Plaintext>): String? {
@ -109,7 +114,12 @@ class ConversationService(private val messageRepository: MessageRepository) {
return child.parents.firstOrNull { it == item.inventoryVector } != null return child.parents.firstOrNull { it == item.inventoryVector } != null
} }
private fun addAncestors(message: Plaintext, result: LinkedList<Plaintext>, messages: LinkedList<Plaintext>, map: MutableMap<InventoryVector, Plaintext>) { private fun addAncestors(
message: Plaintext,
result: LinkedList<Plaintext>,
messages: LinkedList<Plaintext>,
map: MutableMap<InventoryVector, Plaintext>
) {
for (parentKey in message.parents) { for (parentKey in message.parents) {
map.remove(parentKey)?.let { map.remove(parentKey)?.let {
messages.remove(it) messages.remove(it)

View File

@ -29,7 +29,7 @@ object DebugUtils {
try { try {
val f = File(System.getProperty("user.home") + "/jabit.error/" + objectMessage.inventoryVector + ".inv") val f = File(System.getProperty("user.home") + "/jabit.error/" + objectMessage.inventoryVector + ".inv")
f.createNewFile() f.createNewFile()
objectMessage.write(FileOutputStream(f)) objectMessage.writer().write(FileOutputStream(f))
} catch (e: IOException) { } catch (e: IOException) {
LOG.debug(e.message, e) LOG.debug(e.message, e)
} }

View File

@ -25,21 +25,21 @@ import java.nio.ByteBuffer
* https://bitmessage.org/wiki/Protocol_specification#Common_structures * https://bitmessage.org/wiki/Protocol_specification#Common_structures
*/ */
object Decode { object Decode {
@JvmStatic fun shortVarBytes(`in`: InputStream, counter: AccessCounter): ByteArray { @JvmStatic fun shortVarBytes(input: InputStream, counter: AccessCounter): ByteArray {
val length = uint16(`in`, counter) val length = uint16(input, counter)
return bytes(`in`, length, counter) return bytes(input, length, counter)
} }
@JvmStatic @JvmOverloads fun varBytes(`in`: InputStream, counter: AccessCounter? = null): ByteArray { @JvmStatic @JvmOverloads fun varBytes(input: InputStream, counter: AccessCounter? = null): ByteArray {
val length = varInt(`in`, counter).toInt() val length = varInt(input, counter).toInt()
return bytes(`in`, length, counter) return bytes(input, length, counter)
} }
@JvmStatic @JvmOverloads fun bytes(`in`: InputStream, count: Int, counter: AccessCounter? = null): ByteArray { @JvmStatic @JvmOverloads fun bytes(input: InputStream, count: Int, counter: AccessCounter? = null): ByteArray {
val result = ByteArray(count) val result = ByteArray(count)
var off = 0 var off = 0
while (off < count) { while (off < count) {
val read = `in`.read(result, off, count - off) val read = input.read(result, off, count - off)
if (read < 0) { if (read < 0) {
throw IOException("Unexpected end of stream, wanted to read $count bytes but only got $off") throw IOException("Unexpected end of stream, wanted to read $count bytes but only got $off")
} }
@ -49,60 +49,58 @@ object Decode {
return result return result
} }
@JvmStatic fun varIntList(`in`: InputStream): LongArray { @JvmStatic fun varIntList(input: InputStream): LongArray {
val length = varInt(`in`).toInt() val length = varInt(input).toInt()
val result = LongArray(length) val result = LongArray(length)
for (i in 0..length - 1) { for (i in 0 until length) {
result[i] = varInt(`in`) result[i] = varInt(input)
} }
return result return result
} }
@JvmStatic @JvmOverloads fun varInt(`in`: InputStream, counter: AccessCounter? = null): Long { @JvmStatic @JvmOverloads fun varInt(input: InputStream, counter: AccessCounter? = null): Long {
val first = `in`.read() val first = input.read()
AccessCounter.inc(counter) AccessCounter.inc(counter)
when (first) { when (first) {
0xfd -> return uint16(`in`, counter).toLong() 0xfd -> return uint16(input, counter).toLong()
0xfe -> return uint32(`in`, counter) 0xfe -> return uint32(input, counter)
0xff -> return int64(`in`, counter) 0xff -> return int64(input, counter)
else -> return first.toLong() else -> return first.toLong()
} }
} }
@JvmStatic fun uint8(`in`: InputStream): Int { @JvmStatic fun uint8(input: InputStream): Int = input.read()
return `in`.read()
}
@JvmStatic @JvmOverloads fun uint16(`in`: InputStream, counter: AccessCounter? = null): Int { @JvmStatic @JvmOverloads fun uint16(input: InputStream, counter: AccessCounter? = null): Int {
AccessCounter.inc(counter, 2) AccessCounter.inc(counter, 2)
return `in`.read() shl 8 or `in`.read() return input.read() shl 8 or input.read()
} }
@JvmStatic @JvmOverloads fun uint32(`in`: InputStream, counter: AccessCounter? = null): Long { @JvmStatic @JvmOverloads fun uint32(input: InputStream, counter: AccessCounter? = null): Long {
AccessCounter.inc(counter, 4) AccessCounter.inc(counter, 4)
return (`in`.read() shl 24 or (`in`.read() shl 16) or (`in`.read() shl 8) or `in`.read()).toLong() return (input.read() shl 24 or (input.read() shl 16) or (input.read() shl 8) or input.read()).toLong()
} }
@JvmStatic fun uint32(`in`: ByteBuffer): Long { @JvmStatic fun uint32(input: ByteBuffer): Long {
return (u(`in`.get()) shl 24 or (u(`in`.get()) shl 16) or (u(`in`.get()) shl 8) or u(`in`.get())).toLong() return (u(input.get()) shl 24 or (u(input.get()) shl 16) or (u(input.get()) shl 8) or u(input.get())).toLong()
} }
@JvmStatic @JvmOverloads fun int32(`in`: InputStream, counter: AccessCounter? = null): Int { @JvmStatic @JvmOverloads fun int32(input: InputStream, counter: AccessCounter? = null): Int {
AccessCounter.inc(counter, 4) AccessCounter.inc(counter, 4)
return ByteBuffer.wrap(bytes(`in`, 4)).int return ByteBuffer.wrap(bytes(input, 4)).int
} }
@JvmStatic @JvmOverloads fun int64(`in`: InputStream, counter: AccessCounter? = null): Long { @JvmStatic @JvmOverloads fun int64(input: InputStream, counter: AccessCounter? = null): Long {
AccessCounter.inc(counter, 8) AccessCounter.inc(counter, 8)
return ByteBuffer.wrap(bytes(`in`, 8)).long return ByteBuffer.wrap(bytes(input, 8)).long
} }
@JvmStatic @JvmOverloads fun varString(`in`: InputStream, counter: AccessCounter? = null): String { @JvmStatic @JvmOverloads fun varString(input: InputStream, counter: AccessCounter? = null): String {
val length = varInt(`in`, counter).toInt() val length = varInt(input, counter).toInt()
// technically, it says the length in characters, but I think this one might be correct // technically, it says the length in characters, but I think this one might be correct
// otherwise it will get complicated, as we'll need to read UTF-8 char by char... // otherwise it will get complicated, as we'll need to read UTF-8 char by char...
return String(bytes(`in`, length, counter)) return String(bytes(input, length, counter))
} }
/** /**

View File

@ -26,53 +26,63 @@ import java.nio.ByteBuffer
* https://bitmessage.org/wiki/Protocol_specification#Common_structures * https://bitmessage.org/wiki/Protocol_specification#Common_structures
*/ */
object Encode { object Encode {
@JvmStatic fun varIntList(values: LongArray, stream: OutputStream) { @JvmStatic
fun varIntList(values: LongArray, stream: OutputStream) {
varInt(values.size, 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, 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
@JvmStatic fun varInt(value: Long, buffer: ByteBuffer) { fun varInt(value: Number, buffer: ByteBuffer) {
if (value < 0) { val longValue = value.toLong()
when {
longValue < 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.
// Please be aware that this might be an error due to a smaller negative value being cast to long. // Please be aware that this might be an error due to a smaller negative value being cast to long.
// Normally, negative values shouldn't occur within the protocol, and longs large enough for being // Normally, negative values shouldn't occur within the protocol, and longs large enough for being
// recognized as negatives aren't realistic. // recognized as negatives aren't realistic.
buffer.put(0xff.toByte()) buffer.put(0xff.toByte())
buffer.putLong(value) buffer.putLong(longValue)
} else if (value < 0xfd) { }
longValue < 0xfd -> {
buffer.put(value.toByte()) buffer.put(value.toByte())
} else if (value <= 0xffffL) { }
longValue <= 0xffffL -> {
buffer.put(0xfd.toByte()) buffer.put(0xfd.toByte())
buffer.putShort(value.toShort()) buffer.putShort(value.toShort())
} else if (value <= 0xffffffffL) { }
longValue <= 0xffffffffL -> {
buffer.put(0xfe.toByte()) buffer.put(0xfe.toByte())
buffer.putInt(value.toInt()) buffer.putInt(value.toInt())
} else { }
else -> {
buffer.put(0xff.toByte()) buffer.put(0xff.toByte())
buffer.putLong(value) buffer.putLong(longValue)
}
} }
} }
@JvmStatic fun varInt(value: Int) = varInt(value.toLong()) @JvmStatic
@JvmStatic fun varInt(value: Long): ByteArray { fun varInt(value: Number): ByteArray {
val buffer = ByteBuffer.allocate(9) val buffer = ByteBuffer.allocate(9)
varInt(value, buffer) varInt(value, buffer)
buffer.flip() buffer.flip()
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
@JvmStatic @JvmOverloads fun varInt(value: Long, stream: OutputStream, counter: AccessCounter? = null) { @JvmOverloads
fun varInt(value: Number, stream: OutputStream, counter: AccessCounter? = null) {
val buffer = ByteBuffer.allocate(9) val buffer = ByteBuffer.allocate(9)
varInt(value, buffer) varInt(value, buffer)
buffer.flip() buffer.flip()
@ -80,46 +90,51 @@ object Encode {
AccessCounter.inc(counter, buffer.limit()) AccessCounter.inc(counter, buffer.limit())
} }
@JvmStatic @JvmOverloads fun int8(value: Long, stream: OutputStream, counter: AccessCounter? = null) = int8(value.toInt(), stream, counter) @JvmStatic
@JvmStatic @JvmOverloads fun int8(value: Int, stream: OutputStream, counter: AccessCounter? = null) { @JvmOverloads
stream.write(value) fun int8(value: Number, stream: OutputStream, counter: AccessCounter? = null) {
stream.write(value.toInt())
AccessCounter.inc(counter) AccessCounter.inc(counter)
} }
@JvmStatic @JvmOverloads fun int16(value: Long, stream: OutputStream, counter: AccessCounter? = null) = int16(value.toShort(), stream, counter) @JvmStatic
@JvmStatic @JvmOverloads fun int16(value: Int, stream: OutputStream, counter: AccessCounter? = null) = int16(value.toShort(), stream, counter) @JvmOverloads
@JvmStatic @JvmOverloads fun int16(value: Short, stream: OutputStream, counter: AccessCounter? = null) { fun int16(value: Number, stream: OutputStream, counter: AccessCounter? = null) {
stream.write(ByteBuffer.allocate(2).putShort(value).array()) stream.write(ByteBuffer.allocate(2).putShort(value.toShort()).array())
AccessCounter.inc(counter, 2) AccessCounter.inc(counter, 2)
} }
@JvmStatic fun int16(value: Long, buffer: ByteBuffer) = int16(value.toShort(), buffer) @JvmStatic
@JvmStatic fun int16(value: Int, buffer: ByteBuffer) = int16(value.toShort(), buffer) fun int16(value: Number, buffer: ByteBuffer) {
@JvmStatic fun int16(value: Short, buffer: ByteBuffer) { buffer.putShort(value.toShort())
buffer.putShort(value)
} }
@JvmStatic @JvmOverloads fun int32(value: Long, stream: OutputStream, counter: AccessCounter? = null) = int32(value.toInt(), stream, counter) @JvmStatic
@JvmStatic @JvmOverloads fun int32(value: Int, stream: OutputStream, counter: AccessCounter? = null) { @JvmOverloads
stream.write(ByteBuffer.allocate(4).putInt(value).array()) fun int32(value: Number, stream: OutputStream, counter: AccessCounter? = null) {
stream.write(ByteBuffer.allocate(4).putInt(value.toInt()).array())
AccessCounter.inc(counter, 4) AccessCounter.inc(counter, 4)
} }
@JvmStatic fun int32(value: Long, buffer: ByteBuffer) = int32(value.toInt(), buffer) @JvmStatic
@JvmStatic fun int32(value: Int, buffer: ByteBuffer) { fun int32(value: Number, buffer: ByteBuffer) {
buffer.putInt(value) buffer.putInt(value.toInt())
} }
@JvmStatic @JvmOverloads fun int64(value: Long, stream: OutputStream, counter: AccessCounter? = null) { @JvmStatic
stream.write(ByteBuffer.allocate(8).putLong(value).array()) @JvmOverloads
fun int64(value: Number, stream: OutputStream, counter: AccessCounter? = null) {
stream.write(ByteBuffer.allocate(8).putLong(value.toLong()).array())
AccessCounter.inc(counter, 8) AccessCounter.inc(counter, 8)
} }
@JvmStatic fun int64(value: Long, buffer: ByteBuffer) { @JvmStatic
buffer.putLong(value) fun int64(value: Number, buffer: ByteBuffer) {
buffer.putLong(value.toLong())
} }
@JvmStatic fun varString(value: String, out: OutputStream) { @JvmStatic
fun varString(value: String, out: OutputStream) {
val bytes = value.toByteArray(charset("utf-8")) val bytes = value.toByteArray(charset("utf-8"))
// Technically, it says the length in characters, but I think this one might be correct. // Technically, it says the length in characters, but I think this one might be correct.
// It doesn't really matter, as only ASCII characters are being used. // It doesn't really matter, as only ASCII characters are being used.
@ -128,7 +143,8 @@ object Encode {
out.write(bytes) out.write(bytes)
} }
@JvmStatic fun varString(value: String, buffer: ByteBuffer) { @JvmStatic
fun varString(value: String, buffer: ByteBuffer) {
val bytes = value.toByteArray() val bytes = value.toByteArray()
// Technically, it says the length in characters, but I think this one might be correct. // Technically, it says the length in characters, but I think this one might be correct.
// It doesn't really matter, as only ASCII characters are being used. // It doesn't really matter, as only ASCII characters are being used.
@ -137,12 +153,14 @@ object Encode {
buffer.put(bytes) buffer.put(bytes)
} }
@JvmStatic fun varBytes(data: ByteArray, out: OutputStream) { @JvmStatic
fun varBytes(data: ByteArray, out: OutputStream) {
varInt(data.size.toLong(), out) varInt(data.size.toLong(), out)
out.write(data) out.write(data)
} }
@JvmStatic fun varBytes(data: ByteArray, buffer: ByteBuffer) { @JvmStatic
fun varBytes(data: ByteArray, buffer: ByteBuffer) {
varInt(data.size.toLong(), buffer) varInt(data.size.toLong(), buffer)
buffer.put(data) buffer.put(data)
} }
@ -152,9 +170,10 @@ object Encode {
* @param streamable the object to be serialized * @param streamable the object to be serialized
* @return an array of bytes representing the given streamable object. * @return an array of bytes representing the given streamable object.
*/ */
@JvmStatic fun bytes(streamable: Streamable): ByteArray { @JvmStatic
fun bytes(streamable: Streamable): ByteArray {
val stream = ByteArrayOutputStream() val stream = ByteArrayOutputStream()
streamable.write(stream) streamable.writer().write(stream)
return stream.toByteArray() return stream.toByteArray()
} }
@ -163,9 +182,10 @@ object Encode {
* @param padding the result will be padded such that its length is a multiple of *padding* * @param padding the result will be padded such that its length is a multiple of *padding*
* @return the bytes of the given [Streamable] object, 0-padded such that the final length is x*padding. * @return the bytes of the given [Streamable] object, 0-padded such that the final length is x*padding.
*/ */
@JvmStatic fun bytes(streamable: Streamable, padding: Int): ByteArray { @JvmStatic
fun bytes(streamable: Streamable, padding: Int): ByteArray {
val stream = ByteArrayOutputStream() val stream = ByteArrayOutputStream()
streamable.write(stream) streamable.writer().write(stream)
val offset = padding - stream.size() % padding val offset = padding - stream.size() % padding
val length = stream.size() + offset val length = stream.size() + offset
val result = ByteArray(length) val result = ByteArray(length)

View File

@ -35,11 +35,9 @@ 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
import com.nhaarman.mockito_kotlin.* import com.nhaarman.mockito_kotlin.*
import org.hamcrest.CoreMatchers.`is` import org.junit.jupiter.api.Assertions.*
import org.hamcrest.CoreMatchers.notNullValue import org.junit.jupiter.api.BeforeEach
import org.junit.Assert.* import org.junit.jupiter.api.Test
import org.junit.Before
import org.junit.Test
import java.util.* import java.util.*
import kotlin.concurrent.thread import kotlin.concurrent.thread
@ -47,15 +45,16 @@ import kotlin.concurrent.thread
* @author Christian Basler * @author Christian Basler
*/ */
class BitmessageContextTest { class BitmessageContextTest {
private var listener: BitmessageContext.Listener = mock() private var testListener: BitmessageContext.Listener = mock()
private val inventory = spy(TestInventory()) private val testInventory = spy(TestInventory())
private val testPowRepo = spy(object : ProofOfWorkRepository { private val testPowRepo = spy(object : ProofOfWorkRepository {
internal var items: MutableMap<InventoryVector, ProofOfWorkRepository.Item> = HashMap() internal var items: MutableMap<InventoryVector, ProofOfWorkRepository.Item> = HashMap()
internal var added = 0 internal var added = 0
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)] ?: throw IllegalArgumentException("${hex(initialHash)} not found in $items") return items[InventoryVector(initialHash)]
?: throw IllegalArgumentException("${hex(initialHash)} not found in $items")
} }
override fun getItems(): List<ByteArray> { override fun getItems(): List<ByteArray> {
@ -72,7 +71,10 @@ class BitmessageContextTest {
} }
override fun putObject(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) { override fun putObject(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) {
items.put(InventoryVector(cryptography().getInitialHash(objectMessage)), ProofOfWorkRepository.Item(objectMessage, nonceTrialsPerByte, extraBytes)) items.put(
InventoryVector(cryptography().getInitialHash(objectMessage)),
ProofOfWorkRepository.Item(objectMessage, nonceTrialsPerByte, extraBytes)
)
added++ added++
} }
@ -93,26 +95,27 @@ class BitmessageContextTest {
thread { callback.onNonceCalculated(initialHash, ByteArray(8)) } thread { callback.onNonceCalculated(initialHash, ByteArray(8)) }
} }
}) })
private var ctx = BitmessageContext.Builder() private var ctx = BitmessageContext.build {
.addressRepo(mock()) addressRepo = mock()
.cryptography(BouncyCryptography()) cryptography = BouncyCryptography()
.inventory(inventory) inventory = testInventory
.listener(listener) listener = testListener
.messageRepo(mock()) labelRepo = mock()
.networkHandler(mock { messageRepo = mock()
networkHandler = mock {
on { getNetworkStatus() } doReturn Property("test", "mocked") on { getNetworkStatus() } doReturn Property("test", "mocked")
}) }
.nodeRegistry(mock()) nodeRegistry = mock()
.labeler(spy(DefaultLabeler())) labeler = spy(DefaultLabeler())
.powRepo(testPowRepo) proofOfWorkRepo = testPowRepo
.proofOfWorkEngine(testPowEngine) proofOfWorkEngine = testPowEngine
.build() }
init { init {
TTL.msg = 2 * MINUTE TTL.msg = 2 * MINUTE
} }
@Before @BeforeEach
fun setUp() { fun setUp() {
testPowRepo.reset() testPowRepo.reset()
} }
@ -125,7 +128,7 @@ class BitmessageContextTest {
ctx.addContact(contact) ctx.addContact(contact)
verify(ctx.addresses, timeout(1000).atLeastOnce()).save(eq(contact)) verify(ctx.addresses, timeout(1000).atLeastOnce()).save(eq(contact))
verify(testPowEngine, timeout(1000)).calculateNonce(any(), any(), any()) verify(testPowEngine, timeout(1000)).calculateNonce(any(), any(), any<ProofOfWorkEngine.Callback>())
} }
@Test @Test
@ -138,12 +141,12 @@ class BitmessageContextTest {
ctx.addContact(contact) ctx.addContact(contact)
verify(ctx.addresses, times(1)).save(contact) verify(ctx.addresses, times(1)).save(contact)
verify(testPowEngine, never()).calculateNonce(any(), any(), any()) verify(testPowEngine, never()).calculateNonce(any(), any(), any<ProofOfWorkEngine.Callback>())
} }
@Test @Test
fun `ensure V2Pubkey is not requested if it exists in inventory`() { fun `ensure V2Pubkey is not requested if it exists in inventory`() {
inventory.init( testInventory.init(
"V1Msg.payload", "V1Msg.payload",
"V2GetPubkey.payload", "V2GetPubkey.payload",
"V2Pubkey.payload", "V2Pubkey.payload",
@ -161,12 +164,12 @@ class BitmessageContextTest {
ctx.addContact(contact) ctx.addContact(contact)
verify(ctx.addresses, atLeastOnce()).save(contact) verify(ctx.addresses, atLeastOnce()).save(contact)
verify(testPowEngine, never()).calculateNonce(any(), any(), any()) verify(testPowEngine, never()).calculateNonce(any(), any(), any<ProofOfWorkEngine.Callback>())
} }
@Test @Test
fun `ensure V4Pubkey is not requested if it exists in inventory`() { fun `ensure V4Pubkey is not requested if it exists in inventory`() {
inventory.init( testInventory.init(
"V1Msg.payload", "V1Msg.payload",
"V2GetPubkey.payload", "V2GetPubkey.payload",
"V2Pubkey.payload", "V2Pubkey.payload",
@ -185,14 +188,14 @@ class BitmessageContextTest {
ctx.addContact(contact) ctx.addContact(contact)
verify(ctx.addresses, atLeastOnce()).save(any()) verify(ctx.addresses, atLeastOnce()).save(any())
verify(testPowEngine, never()).calculateNonce(any(), any(), any()) verify(testPowEngine, never()).calculateNonce(any(), any(), any<ProofOfWorkEngine.Callback>())
} }
@Test @Test
fun `ensure subscription is added and existing broadcasts retrieved`() { fun `ensure subscription is added and existing broadcasts retrieved`() {
val address = BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ") val address = BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ")
inventory.init( testInventory.init(
"V4Broadcast.payload", "V4Broadcast.payload",
"V5Broadcast.payload" "V5Broadcast.payload"
) )
@ -201,61 +204,74 @@ class BitmessageContextTest {
ctx.addSubscribtion(address) ctx.addSubscribtion(address)
verify(ctx.addresses, atLeastOnce()).save(address) verify(ctx.addresses, atLeastOnce()).save(address)
assertThat(address.isSubscribed, `is`(true)) assertTrue(address.isSubscribed)
verify(ctx.internals.inventory).getObjects(eq(address.stream), any(), any()) verify(ctx.internals.inventory).getObjects(eq(address.stream), any(), any())
verify(listener).receive(any()) verify(testListener).receive(any())
} }
@Test @Test
fun `ensure identity is created`() { fun `ensure identity is created`() {
assertThat(ctx.createIdentity(false), notNullValue()) assertNotNull(ctx.createIdentity(false))
} }
@Test @Test
fun `ensure message is sent`() { fun `ensure message is sent`() {
ctx.send(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), TestUtils.loadContact(), ctx.send(
"Subject", "Message") TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), TestUtils.loadContact(),
"Subject", "Message"
)
verify(ctx.internals.proofOfWorkRepository, timeout(10000)).putObject( verify(ctx.internals.proofOfWorkRepository, timeout(10000)).putObject(
argThat { payload.type == ObjectType.MSG }, eq(1000L), eq(1000L)) argThat { payload.type == ObjectType.MSG }, eq(1000L), eq(1000L)
)
assertEquals(2, testPowRepo.added) assertEquals(2, testPowRepo.added)
verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat<Plaintext> { type == Type.MSG }) verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat<Plaintext> { type == Type.MSG })
} }
@Test @Test
fun `ensure pubkey is requested if it is missing`() { fun `ensure pubkey is requested if it is missing`() {
ctx.send(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), ctx.send(
TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"),
BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"), BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"),
"Subject", "Message") "Subject", "Message"
)
verify(testPowRepo, timeout(10000).atLeastOnce()) verify(testPowRepo, timeout(10000).atLeastOnce())
.putObject(argThat { payload.type == ObjectType.GET_PUBKEY }, eq(1000L), eq(1000L)) .putObject(argThat { payload.type == ObjectType.GET_PUBKEY }, eq(1000L), eq(1000L))
verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat<Plaintext> { type == Type.MSG }) verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat<Plaintext> { type == Type.MSG })
} }
@Test(expected = IllegalArgumentException::class) @Test
fun `ensure sender must be identity`() { fun `ensure sender must be identity`() {
ctx.send(BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"), assertThrows(IllegalArgumentException::class.java) {
ctx.send(
BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"), BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"),
"Subject", "Message") BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"),
"Subject", "Message"
)
}
} }
@Test @Test
fun `ensure broadcast is sent`() { fun `ensure broadcast is sent`() {
ctx.broadcast(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), ctx.broadcast(
"Subject", "Message") TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"),
"Subject", "Message"
)
verify(ctx.internals.proofOfWorkRepository, timeout(1000).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(testPowEngine).calculateNonce(any(), any(), any()) verify(testPowEngine).calculateNonce(any(), any(), any<ProofOfWorkEngine.Callback>())
verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat<Plaintext> { type == Type.BROADCAST }) verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat<Plaintext> { type == Type.BROADCAST })
} }
@Test(expected = IllegalArgumentException::class) @Test
fun `ensure sender without private key throws exception`() { fun `ensure sender without private key throws exception`() {
assertThrows(IllegalArgumentException::class.java) {
val msg = Plaintext.Builder(Type.BROADCAST) val msg = Plaintext.Builder(Type.BROADCAST)
.from(BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")) .from(BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
.message("Subject", "Message") .message("Subject", "Message")
.build() .build()
ctx.send(msg) ctx.send(msg)
} }
}
@Test @Test
fun `ensure chan is joined`() { fun `ensure chan is joined`() {
@ -325,6 +341,6 @@ class BitmessageContextTest {
@Test @Test
fun `ensure status contains user agent`() { fun `ensure status contains user agent`() {
val userAgent = ctx.status().getProperty("user agent")?.value.toString() val userAgent = ctx.status().getProperty("user agent")?.value.toString()
assertThat(userAgent, `is`("/Jabit:${BitmessageContext.version}/")) assertEquals("/Jabit:${BitmessageContext.version}/", userAgent)
} }
} }

View File

@ -21,9 +21,9 @@ import ch.dissem.bitmessage.entity.payload.V4Broadcast
import ch.dissem.bitmessage.entity.payload.V5Broadcast import ch.dissem.bitmessage.entity.payload.V5Broadcast
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 org.junit.Assert.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.Assert.assertTrue import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.Test import org.junit.jupiter.api.Test
class DecryptionTest : TestBase() { class DecryptionTest : TestBase() {
@Test @Test

View File

@ -34,8 +34,8 @@ import ch.dissem.bitmessage.utils.TestUtils
import ch.dissem.bitmessage.utils.UnixTime.MINUTE import ch.dissem.bitmessage.utils.UnixTime.MINUTE
import ch.dissem.bitmessage.utils.UnixTime.now import ch.dissem.bitmessage.utils.UnixTime.now
import com.nhaarman.mockito_kotlin.* import com.nhaarman.mockito_kotlin.*
import org.junit.Before import org.junit.jupiter.api.BeforeAll
import org.junit.Test import org.junit.jupiter.api.Test
/** /**
* @author Christian Basler * @author Christian Basler
@ -47,7 +47,7 @@ class DefaultMessageListenerTest : TestBase() {
cryptography = BouncyCryptography() cryptography = BouncyCryptography()
) )
@Before @BeforeAll
fun setUp() { fun setUp() {
listener = ctx.networkListener as DefaultMessageListener listener = ctx.networkListener as DefaultMessageListener
} }

View File

@ -24,9 +24,9 @@ import ch.dissem.bitmessage.entity.valueobject.PrivateKey
import ch.dissem.bitmessage.utils.Singleton.cryptography import ch.dissem.bitmessage.utils.Singleton.cryptography
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 org.junit.Assert.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.Assert.assertNotNull import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.Test import org.junit.jupiter.api.Test
class EncryptionTest : TestBase() { class EncryptionTest : TestBase() {
@Test @Test

View File

@ -23,14 +23,14 @@ import ch.dissem.bitmessage.entity.Plaintext.Type.MSG
import ch.dissem.bitmessage.entity.payload.GenericPayload import ch.dissem.bitmessage.entity.payload.GenericPayload
import ch.dissem.bitmessage.entity.payload.Msg import ch.dissem.bitmessage.entity.payload.Msg
import ch.dissem.bitmessage.ports.Cryptography import ch.dissem.bitmessage.ports.Cryptography
import ch.dissem.bitmessage.ports.ProofOfWorkEngine
import ch.dissem.bitmessage.ports.ProofOfWorkRepository import ch.dissem.bitmessage.ports.ProofOfWorkRepository
import ch.dissem.bitmessage.utils.Singleton import ch.dissem.bitmessage.utils.Singleton
import ch.dissem.bitmessage.utils.TestUtils import ch.dissem.bitmessage.utils.TestUtils
import com.nhaarman.mockito_kotlin.* import com.nhaarman.mockito_kotlin.*
import org.hamcrest.CoreMatchers.equalTo import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.Assert.assertThat import org.junit.jupiter.api.BeforeEach
import org.junit.Before import org.junit.jupiter.api.Test
import org.junit.Test
import java.util.* import java.util.*
import kotlin.properties.Delegates import kotlin.properties.Delegates
@ -43,7 +43,7 @@ class ProofOfWorkServiceTest {
private var obj by Delegates.notNull<ObjectMessage>() private var obj by Delegates.notNull<ObjectMessage>()
@Before @BeforeEach
fun setUp() { fun setUp() {
cryptography = spy(BouncyCryptography()) cryptography = spy(BouncyCryptography())
Singleton.initialize(cryptography) Singleton.initialize(cryptography)
@ -65,11 +65,11 @@ class ProofOfWorkServiceTest {
fun `ensure missing proof of work is done`() { fun `ensure missing proof of work is done`() {
whenever(ctx.proofOfWorkRepository.getItems()).thenReturn(Arrays.asList<ByteArray>(ByteArray(64))) whenever(ctx.proofOfWorkRepository.getItems()).thenReturn(Arrays.asList<ByteArray>(ByteArray(64)))
whenever(ctx.proofOfWorkRepository.getItem(any())).thenReturn(ProofOfWorkRepository.Item(obj, 1001, 1002)) whenever(ctx.proofOfWorkRepository.getItem(any())).thenReturn(ProofOfWorkRepository.Item(obj, 1001, 1002))
doNothing().whenever(cryptography).doProofOfWork(any(), any(), any(), any()) doNothing().whenever(cryptography).doProofOfWork(any(), any(), any(), any<ProofOfWorkEngine.Callback>())
ctx.proofOfWorkService.doMissingProofOfWork(10) ctx.proofOfWorkService.doMissingProofOfWork(10)
verify(cryptography, timeout(1000)).doProofOfWork(eq(obj), eq(1001L), eq(1002L), any()) verify(cryptography, timeout(1000)).doProofOfWork(eq(obj), eq(1001L), eq(1002L), any<ProofOfWorkEngine.Callback>())
} }
@Test @Test
@ -95,6 +95,6 @@ class ProofOfWorkServiceTest {
verify(ctx.proofOfWorkRepository).removeObject(eq(initialHash)) verify(ctx.proofOfWorkRepository).removeObject(eq(initialHash))
verify(ctx.inventory).storeObject(eq(objectMessage)) verify(ctx.inventory).storeObject(eq(objectMessage))
verify(ctx.networkHandler).offer(eq(objectMessage.inventoryVector)) verify(ctx.networkHandler).offer(eq(objectMessage.inventoryVector))
assertThat(plaintext.inventoryVector, equalTo(objectMessage.inventoryVector)) assertEquals(objectMessage.inventoryVector, plaintext.inventoryVector)
} }
} }

View File

@ -23,8 +23,8 @@ import ch.dissem.bitmessage.entity.payload.Pubkey
import ch.dissem.bitmessage.entity.valueobject.PrivateKey import ch.dissem.bitmessage.entity.valueobject.PrivateKey
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 org.junit.Assert.* import org.junit.jupiter.api.Assertions.*
import org.junit.Test import org.junit.jupiter.api.Test
class SignatureTest : TestBase() { class SignatureTest : TestBase() {
@Test @Test

View File

@ -22,16 +22,15 @@ import ch.dissem.bitmessage.entity.payload.Pubkey.Feature.INCLUDE_DESTINATION
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.utils.* import ch.dissem.bitmessage.utils.*
import org.junit.Assert import org.junit.jupiter.api.Assertions.*
import org.junit.Assert.* import org.junit.jupiter.api.Test
import org.junit.Test
import java.io.IOException import java.io.IOException
import java.util.* 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)) assertEquals(1, Pubkey.Feature.bitfield(DOES_ACK))
assertEquals(2, Pubkey.Feature.bitfield(INCLUDE_DESTINATION)) assertEquals(2, Pubkey.Feature.bitfield(INCLUDE_DESTINATION))
assertEquals(3, Pubkey.Feature.bitfield(DOES_ACK, INCLUDE_DESTINATION)) assertEquals(3, Pubkey.Feature.bitfield(DOES_ACK, INCLUDE_DESTINATION))
} }
@ -74,7 +73,7 @@ class BitmessageAddressTest : TestBase() {
try { try {
address.pubkey = pubkey address.pubkey = pubkey
} catch (e: Exception) { } catch (e: Exception) {
fail(e.message) fail<Unit>(e.message)
} }
} }
@ -82,7 +81,7 @@ class BitmessageAddressTest : TestBase() {
@Test @Test
fun `ensure V3Pubkey can be imported`() { fun `ensure V3Pubkey can be imported`() {
val address = BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ") val address = BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ")
Assert.assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), address.ripe) assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), address.ripe)
val objectMessage = TestUtils.loadObjectMessage(3, "V3Pubkey.payload") val objectMessage = TestUtils.loadObjectMessage(3, "V3Pubkey.payload")
val pubkey = objectMessage.payload as Pubkey val pubkey = objectMessage.payload as Pubkey
@ -90,7 +89,7 @@ class BitmessageAddressTest : TestBase() {
try { try {
address.pubkey = pubkey address.pubkey = pubkey
} catch (e: Exception) { } catch (e: Exception) {
fail(e.message) fail<Unit>(e.message)
} }
assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), pubkey.ripe) assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), pubkey.ripe)
@ -107,7 +106,7 @@ class BitmessageAddressTest : TestBase() {
try { try {
address.pubkey = pubkey address.pubkey = pubkey
} catch (e: Exception) { } catch (e: Exception) {
fail(e.message) fail<Unit>(e.message)
} }
assertTrue(address.has(DOES_ACK)) assertTrue(address.has(DOES_ACK))

File diff suppressed because one or more lines are too long

View File

@ -24,9 +24,8 @@ import ch.dissem.bitmessage.entity.valueobject.extended.Message
import ch.dissem.bitmessage.factory.Factory import ch.dissem.bitmessage.factory.Factory
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 org.hamcrest.Matchers.`is` import org.junit.jupiter.api.Assertions.*
import org.junit.Assert.* import org.junit.jupiter.api.Test
import org.junit.Test
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.ObjectInputStream import java.io.ObjectInputStream
@ -86,16 +85,16 @@ class SerializationTest : TestBase() {
.signature(ByteArray(0)) .signature(ByteArray(0))
.build() .build()
val out = ByteArrayOutputStream() val out = ByteArrayOutputStream()
expected.write(out) expected.writer().write(out)
val `in` = ByteArrayInputStream(out.toByteArray()) val input = ByteArrayInputStream(out.toByteArray())
val actual = Plaintext.read(MSG, `in`) val actual = Plaintext.read(MSG, input)
// Received is automatically set on deserialization, so we'll need to set it to null // Received is automatically set on deserialization, so we'll need to set it to null
val received = Plaintext::class.java.getDeclaredField("received") val received = Plaintext::class.java.getDeclaredField("received")
received.isAccessible = true received.isAccessible = true
received.set(actual, null) received.set(actual, null)
assertThat(expected, `is`(actual)) assertEquals(actual, expected)
} }
@Test @Test
@ -103,17 +102,46 @@ class SerializationTest : TestBase() {
val expected = Plaintext.Builder(MSG) val expected = Plaintext.Builder(MSG)
.from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")) .from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
.to(TestUtils.loadContact()) .to(TestUtils.loadContact())
.message(Message.Builder() .message(
Message.Builder()
.subject("Subject") .subject("Subject")
.body("Message") .body("Message")
.build()) .build()
)
.ackData("ackMessage".toByteArray()) .ackData("ackMessage".toByteArray())
.signature(ByteArray(0)) .signature(ByteArray(0))
.build() .build()
val out = ByteArrayOutputStream() val out = ByteArrayOutputStream()
expected.write(out) expected.writer().write(out)
val `in` = ByteArrayInputStream(out.toByteArray()) val input = ByteArrayInputStream(out.toByteArray())
val actual = Plaintext.read(MSG, `in`) val actual = Plaintext.read(MSG, input)
// Received is automatically set on deserialization, so we'll need to set it to null
val received = Plaintext::class.java.getDeclaredField("received")
received.isAccessible = true
received.set(actual, null)
assertEquals(expected, actual)
}
@Test
fun `ensure plaintext without recipient can be serialized (needed for saving drafts)`() {
val expected = Plaintext.Builder(MSG)
.from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
.message(
Message.Builder()
.subject("Subject")
.body("Message")
.build()
)
.signature(ByteArray(0))
.status(Plaintext.Status.DRAFT)
.build()
val out = ByteArrayOutputStream()
expected.writer().write(out)
val input = ByteArrayInputStream(out.toByteArray())
val actual = Plaintext.read(MSG, input)
actual.status = Plaintext.Status.DRAFT // status isn't serialized, that's OK
// Received is automatically set on deserialization, so we'll need to set it to null // Received is automatically set on deserialization, so we'll need to set it to null
val received = Plaintext::class.java.getDeclaredField("received") val received = Plaintext::class.java.getDeclaredField("received")
@ -136,9 +164,9 @@ class SerializationTest : TestBase() {
assertNotNull(ackMessage1) assertNotNull(ackMessage1)
val out = ByteArrayOutputStream() val out = ByteArrayOutputStream()
expected.write(out) expected.writer().write(out)
val `in` = ByteArrayInputStream(out.toByteArray()) val input = ByteArrayInputStream(out.toByteArray())
val actual = Plaintext.read(MSG, `in`) val actual = Plaintext.read(MSG, input)
// Received is automatically set on deserialization, so we'll need to set it to null // Received is automatically set on deserialization, so we'll need to set it to null
val received = Plaintext::class.java.getDeclaredField("received") val received = Plaintext::class.java.getDeclaredField("received")
@ -159,7 +187,7 @@ class SerializationTest : TestBase() {
val inv = Inv(ivs) val inv = Inv(ivs)
val before = NetworkMessage(inv) val before = NetworkMessage(inv)
val out = ByteArrayOutputStream() val out = ByteArrayOutputStream()
before.write(out) before.writer().write(out)
val after = Factory.getNetworkMessage(3, ByteArrayInputStream(out.toByteArray())) val after = Factory.getNetworkMessage(3, ByteArrayInputStream(out.toByteArray()))
assertNotNull(after) assertNotNull(after)
@ -169,11 +197,11 @@ 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 input = ByteArrayInputStream(data)
val objectMessage = Factory.getObjectMessage(version, `in`, data.size) val objectMessage = Factory.getObjectMessage(version, input, data.size)
val out = ByteArrayOutputStream() val out = ByteArrayOutputStream()
assertNotNull(objectMessage) assertNotNull(objectMessage)
objectMessage!!.write(out) objectMessage!!.writer().write(out)
assertArrayEquals(data, out.toByteArray()) assertArrayEquals(data, out.toByteArray())
assertEquals(expectedPayloadType.canonicalName, objectMessage.payload.javaClass.canonicalName) assertEquals(expectedPayloadType.canonicalName, objectMessage.payload.javaClass.canonicalName)
} }
@ -190,8 +218,8 @@ class SerializationTest : TestBase() {
val oos = ObjectOutputStream(out) val oos = ObjectOutputStream(out)
oos.writeObject(plaintext) oos.writeObject(plaintext)
val `in` = ByteArrayInputStream(out.toByteArray()) val input = ByteArrayInputStream(out.toByteArray())
val ois = ObjectInputStream(`in`) val ois = ObjectInputStream(input)
assertEquals(plaintext, ois.readObject()) assertEquals(plaintext, ois.readObject())
} }
} }

View File

@ -20,19 +20,25 @@ import ch.dissem.bitmessage.utils.Bytes
import ch.dissem.bitmessage.utils.CallbackWaiter import ch.dissem.bitmessage.utils.CallbackWaiter
import ch.dissem.bitmessage.utils.Singleton.cryptography import ch.dissem.bitmessage.utils.Singleton.cryptography
import ch.dissem.bitmessage.utils.TestBase import ch.dissem.bitmessage.utils.TestBase
import org.junit.Assert.assertTrue import org.junit.jupiter.api.Assertions.assertTimeoutPreemptively
import org.junit.Test import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import java.time.Duration.ofSeconds
class ProofOfWorkEngineTest : TestBase() { class ProofOfWorkEngineTest : TestBase() {
@Test(timeout = 90000) @Test
fun `test SimplePOWEngine`() { fun `test SimplePOWEngine`() {
assertTimeoutPreemptively(ofSeconds(90)) {
testPOW(SimplePOWEngine()) testPOW(SimplePOWEngine())
} }
}
@Test(timeout = 90000) @Test
fun `test MultiThreadedPOWEngine`() { fun `test MultiThreadedPOWEngine`() {
assertTimeoutPreemptively(ofSeconds(90)) {
testPOW(MultiThreadedPOWEngine()) testPOW(MultiThreadedPOWEngine())
} }
}
private fun testPOW(engine: ProofOfWorkEngine) { private fun testPOW(engine: ProofOfWorkEngine) {
val initialHash = cryptography().sha512(byteArrayOf(1, 3, 6, 4)) val initialHash = cryptography().sha512(byteArrayOf(1, 3, 6, 4))
@ -65,6 +71,6 @@ class ProofOfWorkEngineTest : TestBase() {
val nonce2 = waiter2.waitForValue()!! val nonce2 = waiter2.waitForValue()!!
println("Calculating nonce1 took ${waiter2.time}ms") println("Calculating nonce1 took ${waiter2.time}ms")
assertTrue(Bytes.lt(cryptography().doubleSha512(nonce2, initialHash2), target2, 8)) assertTrue(Bytes.lt(cryptography().doubleSha512(nonce2, initialHash2), target2, 8))
assertTrue("Second nonce1 must be quicker to find", waiter1.time > waiter2.time) assertTrue(waiter1.time > waiter2.time, "Second nonce1 must be quicker to find")
} }
} }

View File

@ -17,9 +17,8 @@
package ch.dissem.bitmessage.utils package ch.dissem.bitmessage.utils
import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography
import org.hamcrest.Matchers.`is` import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.Assert.assertThat import org.junit.jupiter.api.Test
import org.junit.Test
class Base64Test { class Base64Test {
@Test @Test
@ -29,7 +28,7 @@ class Base64Test {
val data = cryptography.randomBytes(i) val data = cryptography.randomBytes(i)
val string = Base64.encodeToString(data) val string = Base64.encodeToString(data)
val decoded = Base64.decode(string) val decoded = Base64.decode(string)
assertThat(decoded, `is`(data)) assertEquals(data, decoded)
} }
} }
} }

View File

@ -16,10 +16,10 @@
package ch.dissem.bitmessage.utils package ch.dissem.bitmessage.utils
import org.junit.Assert.assertArrayEquals import org.junit.jupiter.api.Assertions.assertArrayEquals
import org.junit.Assert.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.Ignore import org.junit.jupiter.api.Disabled
import org.junit.Test import org.junit.jupiter.api.Test
import java.math.BigInteger import java.math.BigInteger
import java.util.* import java.util.*
@ -46,7 +46,11 @@ class BytesTest {
for (i in 1..255) { for (i in 1..255) {
val bytes = byteArrayOf(0, v.toByte()) val bytes = byteArrayOf(0, v.toByte())
Bytes.inc(bytes, i.toByte()) Bytes.inc(bytes, i.toByte())
assertArrayEquals("value = " + v + "; inc = " + i + "; expected = " + (v + i), TestUtils.int16(v + i), bytes) assertArrayEquals(
TestUtils.int16(v + i),
bytes,
"value = " + v + "; inc = " + i + "; expected = " + (v + i)
)
} }
} }
} }
@ -55,7 +59,7 @@ class BytesTest {
* This test is used to compare different implementations of the single byte lt comparison. It an safely be ignored. * This test is used to compare different implementations of the single byte lt comparison. It an safely be ignored.
*/ */
@Test @Test
@Ignore @Disabled
fun `test lower than single byte`() { fun `test lower than single byte`() {
val a = ByteArray(1) val a = ByteArray(1)
val b = ByteArray(1) val b = ByteArray(1)
@ -85,10 +89,13 @@ class BytesTest {
val a = BigInteger.valueOf(rnd.nextLong()).pow(rnd.nextInt(5) + 1).abs() val a = BigInteger.valueOf(rnd.nextLong()).pow(rnd.nextInt(5) + 1).abs()
val b = BigInteger.valueOf(rnd.nextLong()).pow(rnd.nextInt(5) + 1).abs() val b = BigInteger.valueOf(rnd.nextLong()).pow(rnd.nextInt(5) + 1).abs()
println("a = " + a.toString(16) + "\tb = " + b.toString(16)) println("a = " + a.toString(16) + "\tb = " + b.toString(16))
assertEquals(a.compareTo(b) == -1, Bytes.lt( assertEquals(
a.compareTo(b) == -1, Bytes.lt(
Bytes.expand(a.toByteArray(), 100), Bytes.expand(a.toByteArray(), 100),
Bytes.expand(b.toByteArray(), 100), Bytes.expand(b.toByteArray(), 100),
100)) 100
)
)
} }
} }
} }

View File

@ -16,17 +16,12 @@
package ch.dissem.bitmessage.utils package ch.dissem.bitmessage.utils
import org.junit.Test import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import java.util.LinkedList
import org.junit.Assert.assertEquals
class CollectionsTest { class CollectionsTest {
@Test @Test
fun `ensure select random returns maximum possible items`() { fun `ensure select random returns maximum possible items`() {
val list = LinkedList<Int>() assertEquals(9, Collections.selectRandom(9, listOf(0..9)).size)
list += 0..9
assertEquals(9, Collections.selectRandom(9, list).size)
} }
} }

View File

@ -24,9 +24,8 @@ import ch.dissem.bitmessage.entity.valueobject.extended.Message
import ch.dissem.bitmessage.ports.MessageRepository import ch.dissem.bitmessage.ports.MessageRepository
import ch.dissem.bitmessage.utils.TestUtils.RANDOM import ch.dissem.bitmessage.utils.TestUtils.RANDOM
import com.nhaarman.mockito_kotlin.* import com.nhaarman.mockito_kotlin.*
import org.hamcrest.Matchers.`is` import org.junit.jupiter.api.Assertions
import org.junit.Assert.assertThat import org.junit.jupiter.api.Test
import org.junit.Test
import java.util.* import java.util.*
class ConversationServiceTest : TestBase() { class ConversationServiceTest : TestBase() {
@ -45,7 +44,7 @@ class ConversationServiceTest : TestBase() {
doReturn(expected).whenever(conversationService).getConversation(any<UUID>()) doReturn(expected).whenever(conversationService).getConversation(any<UUID>())
val actual = conversationService.getConversation(UUID.randomUUID()) val actual = conversationService.getConversation(UUID.randomUUID())
assertThat(actual, `is`(expected)) Assertions.assertEquals(expected, actual)
} }
companion object { companion object {
@ -53,57 +52,69 @@ class ConversationServiceTest : TestBase() {
fun conversation(alice: BitmessageAddress, bob: BitmessageAddress): List<Plaintext> { fun conversation(alice: BitmessageAddress, bob: BitmessageAddress): List<Plaintext> {
val result = LinkedList<Plaintext>() val result = LinkedList<Plaintext>()
val older = plaintext(alice, bob, val older = plaintext(
alice, bob,
Message.Builder() Message.Builder()
.subject("hey there") .subject("hey there")
.body("does it work?") .body("does it work?")
.build(), .build(),
Plaintext.Status.SENT) Plaintext.Status.SENT
)
result.add(older) result.add(older)
val root = plaintext(alice, bob, val root = plaintext(
alice, bob,
Message.Builder() Message.Builder()
.subject("new test") .subject("new test")
.body("There's a new test in town!") .body("There's a new test in town!")
.build(), .build(),
Plaintext.Status.SENT) Plaintext.Status.SENT
)
result.add(root) result.add(root)
result.add( result.add(
plaintext(bob, alice, plaintext(
bob, alice,
Message.Builder() Message.Builder()
.subject("Re: new test (1a)") .subject("Re: new test (1a)")
.body("Nice!") .body("Nice!")
.addParent(root) .addParent(root)
.build(), .build(),
Plaintext.Status.RECEIVED) Plaintext.Status.RECEIVED
)
) )
val latest = plaintext(bob, alice, val latest = plaintext(
bob, alice,
Message.Builder() Message.Builder()
.subject("Re: new test (2b)") .subject("Re: new test (2b)")
.body("PS: it did work!") .body("PS: it did work!")
.addParent(root) .addParent(root)
.addParent(older) .addParent(older)
.build(), .build(),
Plaintext.Status.RECEIVED) Plaintext.Status.RECEIVED
)
result.add(latest) result.add(latest)
result.add( result.add(
plaintext(alice, bob, plaintext(
alice, bob,
Message.Builder() Message.Builder()
.subject("Re: new test (2)") .subject("Re: new test (2)")
.body("") .body("")
.addParent(latest) .addParent(latest)
.build(), .build(),
Plaintext.Status.DRAFT) Plaintext.Status.DRAFT
)
) )
return result return result
} }
fun plaintext(from: BitmessageAddress, to: BitmessageAddress, fun plaintext(
content: ExtendedEncoding, status: Plaintext.Status): Plaintext { from: BitmessageAddress, to: BitmessageAddress,
content: ExtendedEncoding, status: Plaintext.Status
): Plaintext {
val builder = Plaintext.Builder(MSG) val builder = Plaintext.Builder(MSG)
.IV(TestUtils.randomInventoryVector()) .IV(TestUtils.randomInventoryVector())
.from(from) .from(from)

View File

@ -16,8 +16,8 @@
package ch.dissem.bitmessage.utils package ch.dissem.bitmessage.utils
import org.junit.Assert.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.Test import org.junit.jupiter.api.Test
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
@ -34,8 +34,8 @@ class DecodeTest {
} }
private fun testCodec(number: Long) { private fun testCodec(number: Long) {
val `is` = ByteArrayOutputStream() val out = ByteArrayOutputStream()
Encode.varInt(number, `is`) Encode.varInt(number, out)
assertEquals(number, Decode.varInt(ByteArrayInputStream(`is`.toByteArray()))) assertEquals(number, Decode.varInt(ByteArrayInputStream(out.toByteArray())))
} }
} }

View File

@ -16,8 +16,9 @@
package ch.dissem.bitmessage.utils package ch.dissem.bitmessage.utils
import org.junit.Assert.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.Test import org.junit.jupiter.api.Assumptions
import org.junit.jupiter.api.Test
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
class EncodeTest { class EncodeTest {
@ -111,7 +112,8 @@ class EncodeTest {
fun checkBytes(stream: ByteArrayOutputStream, vararg bytes: Int) { fun checkBytes(stream: ByteArrayOutputStream, vararg bytes: Int) {
assertEquals(bytes.size, stream.size()) Assumptions.assumeTrue(bytes.size == stream.size())
val streamBytes = stream.toByteArray() val streamBytes = stream.toByteArray()
for (i in bytes.indices) { for (i in bytes.indices) {

View File

@ -16,14 +16,12 @@
package ch.dissem.bitmessage.utils package ch.dissem.bitmessage.utils
import org.junit.Test import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.junit.Assert.assertEquals
class SqlStringsTest { class SqlStringsTest {
@Test @Test
fun `ensure join works with long array`() { fun `ensure join works with long array`() {
val test = longArrayOf(1L, 2L) assertEquals("1, 2", SqlStrings.join(1L, 2L))
assertEquals("1, 2", SqlStrings.join(*test))
} }
} }

View File

@ -16,9 +16,8 @@
package ch.dissem.bitmessage.utils package ch.dissem.bitmessage.utils
import org.junit.Test import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.junit.Assert.assertEquals
class StringsTest { class StringsTest {
@Test @Test

View File

@ -17,15 +17,16 @@
package ch.dissem.bitmessage.utils package ch.dissem.bitmessage.utils
import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography
import org.junit.BeforeClass import org.junit.jupiter.api.BeforeAll
/** /**
* @author Christian Basler * @author Christian Basler
*/ */
open class TestBase { open class TestBase {
companion object { companion object {
@BeforeClass @BeforeAll
@JvmStatic fun setUpClass() { @JvmStatic
fun setUpClass() {
Singleton.initialize(BouncyCryptography()) Singleton.initialize(BouncyCryptography())
} }
} }

View File

@ -18,6 +18,7 @@ package ch.dissem.bitmessage.utils
import ch.dissem.bitmessage.BitmessageContext import ch.dissem.bitmessage.BitmessageContext
import ch.dissem.bitmessage.InternalContext import ch.dissem.bitmessage.InternalContext
import ch.dissem.bitmessage.Preferences
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.payload.V4Pubkey import ch.dissem.bitmessage.entity.payload.V4Pubkey
@ -28,7 +29,7 @@ import ch.dissem.bitmessage.factory.Factory
import ch.dissem.bitmessage.ports.* import ch.dissem.bitmessage.ports.*
import com.nhaarman.mockito_kotlin.mock import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.spy import com.nhaarman.mockito_kotlin.spy
import org.junit.Assert.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.InputStream import java.io.InputStream
@ -39,43 +40,50 @@ import kotlin.NoSuchElementException
* If there's ever a need for this in production code, it should be rewritten to be more efficient. * If there's ever a need for this in production code, it should be rewritten to be more efficient.
*/ */
object TestUtils { object TestUtils {
@JvmField val RANDOM = Random() @JvmField
val RANDOM = Random()
@JvmStatic fun int16(number: Int): ByteArray { @JvmStatic
fun int16(number: Int): ByteArray {
val out = ByteArrayOutputStream() val out = ByteArrayOutputStream()
Encode.int16(number, out) Encode.int16(number, out)
return out.toByteArray() return out.toByteArray()
} }
@JvmStatic fun loadObjectMessage(version: Int, resourceName: String): ObjectMessage { @JvmStatic
fun loadObjectMessage(version: Int, resourceName: String): ObjectMessage {
val data = getBytes(resourceName) val data = getBytes(resourceName)
val `in` = ByteArrayInputStream(data) val input = ByteArrayInputStream(data)
return Factory.getObjectMessage(version, `in`, data.size) ?: throw NoSuchElementException("error loading object message") return Factory.getObjectMessage(version, input, data.size)
?: throw NoSuchElementException("error loading object message")
} }
@JvmStatic fun getBytes(resourceName: String): ByteArray { @JvmStatic
val `in` = javaClass.classLoader.getResourceAsStream(resourceName) fun getBytes(resourceName: String): ByteArray {
val input = javaClass.classLoader.getResourceAsStream(resourceName)
val out = ByteArrayOutputStream() val out = ByteArrayOutputStream()
val buffer = ByteArray(1024) val buffer = ByteArray(1024)
var len = `in`.read(buffer) var len = input.read(buffer)
while (len != -1) { while (len != -1) {
out.write(buffer, 0, len) out.write(buffer, 0, len)
len = `in`.read(buffer) len = input.read(buffer)
} }
return out.toByteArray() return out.toByteArray()
} }
@JvmStatic fun randomInventoryVector(): InventoryVector { @JvmStatic
fun randomInventoryVector(): InventoryVector {
val bytes = ByteArray(32) val bytes = ByteArray(32)
RANDOM.nextBytes(bytes) RANDOM.nextBytes(bytes)
return InventoryVector(bytes) return InventoryVector(bytes)
} }
@JvmStatic fun getResource(resourceName: String): InputStream { @JvmStatic
return javaClass.classLoader.getResourceAsStream(resourceName) fun getResource(resourceName: String): InputStream =
} javaClass.classLoader.getResourceAsStream(resourceName)
@JvmStatic fun loadIdentity(address: String): BitmessageAddress { @JvmStatic
fun loadIdentity(address: String): BitmessageAddress {
val privateKey = PrivateKey.read(TestUtils.getResource(address + ".privkey")) val privateKey = PrivateKey.read(TestUtils.getResource(address + ".privkey"))
val identity = BitmessageAddress(privateKey) val identity = BitmessageAddress(privateKey)
assertEquals(address, identity.address) assertEquals(address, identity.address)
@ -83,7 +91,8 @@ 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 objectMessage = TestUtils.loadObjectMessage(3, "V4Pubkey.payload") val objectMessage = TestUtils.loadObjectMessage(3, "V4Pubkey.payload")
objectMessage.decrypt(address.publicDecryptionKey) objectMessage.decrypt(address.publicDecryptionKey)
@ -91,18 +100,21 @@ object TestUtils {
return address return address
} }
@JvmStatic fun loadPubkey(address: BitmessageAddress) { @JvmStatic
fun loadPubkey(address: BitmessageAddress) {
val bytes = getBytes(address.address + ".pubkey") val bytes = getBytes(address.address + ".pubkey")
val pubkey = Factory.readPubkey(address.version, address.stream, ByteArrayInputStream(bytes), bytes.size, false) val pubkey = Factory.readPubkey(address.version, address.stream, ByteArrayInputStream(bytes), bytes.size, false)
address.pubkey = pubkey address.pubkey = pubkey
} }
@JvmStatic fun mockedInternalContext( @JvmStatic
fun mockedInternalContext(
cryptography: Cryptography = mock {}, cryptography: Cryptography = mock {},
inventory: Inventory = mock {}, inventory: Inventory = mock {},
nodeRegistry: NodeRegistry = mock {}, nodeRegistry: NodeRegistry = mock {},
networkHandler: NetworkHandler = mock {}, networkHandler: NetworkHandler = mock {},
addressRepository: AddressRepository = mock {}, addressRepository: AddressRepository = mock {},
labelRepository: LabelRepository = mock {},
messageRepository: MessageRepository = mock {}, messageRepository: MessageRepository = mock {},
proofOfWorkRepository: ProofOfWorkRepository = mock {}, proofOfWorkRepository: ProofOfWorkRepository = mock {},
proofOfWorkEngine: ProofOfWorkEngine = mock {}, proofOfWorkEngine: ProofOfWorkEngine = mock {},
@ -119,16 +131,19 @@ object TestUtils {
nodeRegistry, nodeRegistry,
networkHandler, networkHandler,
addressRepository, addressRepository,
labelRepository,
messageRepository, messageRepository,
proofOfWorkRepository, proofOfWorkRepository,
proofOfWorkEngine, proofOfWorkEngine,
customCommandHandler, customCommandHandler,
listener, listener,
labeler, labeler,
"/Jabit:TEST/", Preferences().apply {
port, this.userAgent = "/Jabit:TEST/"
connectionTTL, this.port = port
connectionLimit this.connectionTTL = connectionTTL
this.connectionLimit = connectionLimit
}
)) ))
} }
} }

View File

@ -13,7 +13,8 @@ uploadArchives {
dependencies { dependencies {
compile project(':core') compile project(':core')
compile 'org.bouncycastle:bcprov-jdk15on' compile 'org.bouncycastle:bcprov-jdk15on'
testCompile 'junit:junit'
testCompile 'com.nhaarman:mockito-kotlin' testCompile 'com.nhaarman:mockito-kotlin'
testCompile 'org.junit.jupiter:junit-jupiter-api'
testRuntime 'org.junit.jupiter:junit-jupiter-engine'
testCompile project(path: ':core', configuration: 'testArtifacts') testCompile project(path: ':core', configuration: 'testArtifacts')
} }

View File

@ -22,14 +22,15 @@ import ch.dissem.bitmessage.entity.payload.GenericPayload
import ch.dissem.bitmessage.entity.valueobject.PrivateKey import ch.dissem.bitmessage.entity.valueobject.PrivateKey
import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException
import ch.dissem.bitmessage.ports.MultiThreadedPOWEngine import ch.dissem.bitmessage.ports.MultiThreadedPOWEngine
import ch.dissem.bitmessage.ports.ProofOfWorkEngine import ch.dissem.bitmessage.utils.CallbackWaiter
import ch.dissem.bitmessage.utils.* import ch.dissem.bitmessage.utils.Singleton
import ch.dissem.bitmessage.utils.TestUtils
import ch.dissem.bitmessage.utils.UnixTime
import ch.dissem.bitmessage.utils.UnixTime.DAY import ch.dissem.bitmessage.utils.UnixTime.DAY
import ch.dissem.bitmessage.utils.UnixTime.MINUTE import ch.dissem.bitmessage.utils.UnixTime.MINUTE
import ch.dissem.bitmessage.utils.UnixTime.now import ch.dissem.bitmessage.utils.UnixTime.now
import org.hamcrest.CoreMatchers.`is` import org.junit.jupiter.api.Assertions.*
import org.junit.Assert.* import org.junit.jupiter.api.Test
import org.junit.Test
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import javax.xml.bind.DatatypeConverter import javax.xml.bind.DatatypeConverter
@ -63,7 +64,7 @@ class CryptographyTest {
assertArrayEquals(crypto.sha512(TEST_SHA512), crypto.doubleSha512(TEST_VALUE)) assertArrayEquals(crypto.sha512(TEST_SHA512), crypto.doubleSha512(TEST_VALUE))
} }
@Test(expected = InsufficientProofOfWorkException::class) @Test
fun `ensure exception for insufficient proof of work`() { fun `ensure exception for insufficient proof of work`() {
val objectMessage = ObjectMessage.Builder() val objectMessage = ObjectMessage.Builder()
.nonce(ByteArray(8)) .nonce(ByteArray(8))
@ -71,8 +72,10 @@ class CryptographyTest {
.objectType(0) .objectType(0)
.payload(GenericPayload.read(0, 1, ByteArrayInputStream(ByteArray(0)), 0)) .payload(GenericPayload.read(0, 1, ByteArrayInputStream(ByteArray(0)), 0))
.build() .build()
assertThrows(InsufficientProofOfWorkException::class.java) {
crypto.checkProofOfWork(objectMessage, 1000, 1000) crypto.checkProofOfWork(objectMessage, 1000, 1000)
} }
}
@Test @Test
fun `ensure proof of work is calculated correctly`() { fun `ensure proof of work is calculated correctly`() {
@ -89,17 +92,12 @@ class CryptographyTest {
stream = 1 stream = 1
) )
val waiter = CallbackWaiter<ByteArray>() val waiter = CallbackWaiter<ByteArray>()
crypto.doProofOfWork(objectMessage, 1000, 1000, crypto.doProofOfWork(objectMessage, 1000, 1000) { _, nonce -> waiter.setValue(nonce) }
object : ProofOfWorkEngine.Callback {
override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) {
waiter.setValue(nonce)
}
})
objectMessage.nonce = waiter.waitForValue() objectMessage.nonce = waiter.waitForValue()
try { try {
crypto.checkProofOfWork(objectMessage, 1000, 1000) crypto.checkProofOfWork(objectMessage, 1000, 1000)
} catch (e: InsufficientProofOfWorkException) { } catch (e: InsufficientProofOfWorkException) {
fail(e.message) fail<Unit>(e.message)
} }
} }
@ -113,13 +111,15 @@ class CryptographyTest {
assertArrayEquals(data, decrypted) assertArrayEquals(data, decrypted)
} }
@Test(expected = IllegalArgumentException::class) @Test
fun `ensure decryption fails with invalid cypher text`() { fun `ensure decryption fails with invalid cypher text`() {
val data = crypto.randomBytes(128) val data = crypto.randomBytes(128)
val key_e = crypto.randomBytes(32) val key_e = crypto.randomBytes(32)
val iv = crypto.randomBytes(16) val iv = crypto.randomBytes(16)
assertThrows(IllegalArgumentException::class.java) {
crypto.crypt(false, data, key_e, iv) crypto.crypt(false, data, key_e, iv)
} }
}
@Test @Test
fun `ensure multiplication works correctly`() { fun `ensure multiplication works correctly`() {
@ -137,7 +137,7 @@ class CryptographyTest {
val data = crypto.randomBytes(100) val data = crypto.randomBytes(100)
val privateKey = PrivateKey(false, 1, 1000, 1000) val privateKey = PrivateKey(false, 1, 1000, 1000)
val signature = crypto.getSignature(data, privateKey) val signature = crypto.getSignature(data, privateKey)
assertThat(crypto.isSignatureValid(data, signature, privateKey.pubkey), `is`(true)) assertTrue(crypto.isSignatureValid(data, signature, privateKey.pubkey))
} }
@Test @Test
@ -146,18 +146,24 @@ class CryptographyTest {
val privateKey = PrivateKey(false, 1, 1000, 1000) val privateKey = PrivateKey(false, 1, 1000, 1000)
val signature = crypto.getSignature(data, privateKey) val signature = crypto.getSignature(data, privateKey)
data[0]++ data[0]++
assertThat(crypto.isSignatureValid(data, signature, privateKey.pubkey), `is`(false)) assertFalse(crypto.isSignatureValid(data, signature, privateKey.pubkey))
} }
companion object { companion object {
val TEST_VALUE = "teststring".toByteArray() val TEST_VALUE = "teststring".toByteArray()
val TEST_SHA1 = DatatypeConverter.parseHexBinary("" val TEST_SHA1 = DatatypeConverter.parseHexBinary(
+ "b8473b86d4c2072ca9b08bd28e373e8253e865c4") ""
val TEST_SHA512 = DatatypeConverter.parseHexBinary("" + "b8473b86d4c2072ca9b08bd28e373e8253e865c4"
)
val TEST_SHA512 = DatatypeConverter.parseHexBinary(
""
+ "6253b39071e5df8b5098f59202d414c37a17d6a38a875ef5f8c7d89b0212b028" + "6253b39071e5df8b5098f59202d414c37a17d6a38a875ef5f8c7d89b0212b028"
+ "692d3d2090ce03ae1de66c862fa8a561e57ed9eb7935ce627344f742c0931d72") + "692d3d2090ce03ae1de66c862fa8a561e57ed9eb7935ce627344f742c0931d72"
val TEST_RIPEMD160 = DatatypeConverter.parseHexBinary("" )
+ "cd566972b5e50104011a92b59fa8e0b1234851ae") val TEST_RIPEMD160 = DatatypeConverter.parseHexBinary(
""
+ "cd566972b5e50104011a92b59fa8e0b1234851ae"
)
private val crypto = BouncyCryptography() private val crypto = BouncyCryptography()

View File

@ -13,7 +13,8 @@ uploadArchives {
dependencies { dependencies {
compile project(':core') compile project(':core')
compile 'com.madgag.spongycastle:prov' compile 'com.madgag.spongycastle:prov'
testCompile 'junit:junit'
testCompile 'com.nhaarman:mockito-kotlin' testCompile 'com.nhaarman:mockito-kotlin'
testCompile 'org.junit.jupiter:junit-jupiter-api'
testRuntime 'org.junit.jupiter:junit-jupiter-engine'
testCompile project(path: ':core', configuration: 'testArtifacts') testCompile project(path: ':core', configuration: 'testArtifacts')
} }

View File

@ -68,12 +68,12 @@ class CryptographyTest {
@Test(expected = IOException::class) @Test(expected = IOException::class)
fun ensureExceptionForInsufficientProofOfWork() { fun ensureExceptionForInsufficientProofOfWork() {
val objectMessage = ObjectMessage.Builder() val objectMessage = ObjectMessage(
.nonce(ByteArray(8)) nonce = ByteArray(8),
.expiresTime(UnixTime.now + 28 * DAY) expiresTime = UnixTime.now + 28 * DAY,
.objectType(0) payload = GenericPayload.read(0, 1, ByteArrayInputStream(ByteArray(0)), 0),
.payload(GenericPayload.read(0, 1, ByteArrayInputStream(ByteArray(0)), 0)) type = 0
.build() )
crypto.checkProofOfWork(objectMessage, 1000, 1000) crypto.checkProofOfWork(objectMessage, 1000, 1000)
} }
@ -86,10 +86,8 @@ class CryptographyTest {
val objectMessage = ObjectMessage( val objectMessage = ObjectMessage(
nonce = ByteArray(8), nonce = ByteArray(8),
expiresTime = UnixTime.now + 2 * MINUTE, expiresTime = UnixTime.now + 2 * MINUTE,
type = 0,
payload = GenericPayload.read(0, 1, ByteArrayInputStream(ByteArray(0)), 0), payload = GenericPayload.read(0, 1, ByteArrayInputStream(ByteArray(0)), 0),
version = 0, type = 0
stream = 1
) )
val waiter = CallbackWaiter<ByteArray>() val waiter = CallbackWaiter<ByteArray>()
crypto.doProofOfWork(objectMessage, 1000, 1000, crypto.doProofOfWork(objectMessage, 1000, 1000,
@ -154,13 +152,19 @@ class CryptographyTest {
companion object { companion object {
val TEST_VALUE = "teststring".toByteArray() val TEST_VALUE = "teststring".toByteArray()
val TEST_SHA1 = DatatypeConverter.parseHexBinary("" val TEST_SHA1 = DatatypeConverter.parseHexBinary(
+ "b8473b86d4c2072ca9b08bd28e373e8253e865c4") ""
val TEST_SHA512 = DatatypeConverter.parseHexBinary("" + "b8473b86d4c2072ca9b08bd28e373e8253e865c4"
)
val TEST_SHA512 = DatatypeConverter.parseHexBinary(
""
+ "6253b39071e5df8b5098f59202d414c37a17d6a38a875ef5f8c7d89b0212b028" + "6253b39071e5df8b5098f59202d414c37a17d6a38a875ef5f8c7d89b0212b028"
+ "692d3d2090ce03ae1de66c862fa8a561e57ed9eb7935ce627344f742c0931d72") + "692d3d2090ce03ae1de66c862fa8a561e57ed9eb7935ce627344f742c0931d72"
val TEST_RIPEMD160 = DatatypeConverter.parseHexBinary("" )
+ "cd566972b5e50104011a92b59fa8e0b1234851ae") val TEST_RIPEMD160 = DatatypeConverter.parseHexBinary(
""
+ "cd566972b5e50104011a92b59fa8e0b1234851ae"
)
private val crypto = SpongyCryptography() private val crypto = SpongyCryptography()

View File

@ -24,15 +24,16 @@ task fatCapsule(type: FatCapsule) {
} }
dependencies { dependencies {
compile project(':core') implementation project(':core')
compile project(':networking') implementation project(':networking')
compile project(':repositories') implementation project(':repositories')
compile project(':cryptography-bc') implementation project(':cryptography-bc')
compile project(':wif') implementation project(':wif')
compile 'org.slf4j:slf4j-simple' implementation 'org.slf4j:slf4j-simple'
compile 'args4j:args4j' implementation 'args4j:args4j'
compile 'com.h2database:h2' implementation 'com.h2database:h2'
compile 'org.apache.commons:commons-lang3' implementation 'org.apache.commons:commons-text'
testCompile 'junit:junit' testImplementation 'com.nhaarman:mockito-kotlin'
testCompile 'com.nhaarman:mockito-kotlin' testImplementation 'org.junit.jupiter:junit-jupiter-api'
testRuntime 'org.junit.jupiter:junit-jupiter-engine'
} }

View File

@ -22,7 +22,7 @@ import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.payload.Pubkey; import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.valueobject.Label; import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.entity.valueobject.extended.Message; import ch.dissem.bitmessage.entity.valueobject.extended.Message;
import org.apache.commons.lang3.text.WordUtils; import org.apache.commons.text.WordUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -115,6 +115,7 @@ public class Application {
System.out.println(ctx.status()); System.out.println(ctx.status());
System.out.println(); System.out.println();
System.out.println("c) cleanup inventory"); System.out.println("c) cleanup inventory");
System.out.println("n) remove known nodes");
System.out.println("r) resend unacknowledged messages"); System.out.println("r) resend unacknowledged messages");
System.out.println(COMMAND_BACK); System.out.println(COMMAND_BACK);
@ -123,6 +124,9 @@ public class Application {
case "c": case "c":
ctx.cleanup(); ctx.cleanup();
break; break;
case "n":
ctx.internals().getNodeRegistry().cleanup();
break;
case "r": case "r":
ctx.resendUnacknowledgedMessages(); ctx.resendUnacknowledgedMessages();
break; break;
@ -285,7 +289,7 @@ public class Application {
} }
private void labels() { private void labels() {
List<Label> labels = ctx.messages().getLabels(); List<Label> labels = ctx.labels().getLabels();
String command; String command;
do { do {
System.out.println(); System.out.println();

View File

@ -24,6 +24,7 @@ import ch.dissem.bitmessage.ports.NodeRegistry;
import ch.dissem.bitmessage.repository.*; import ch.dissem.bitmessage.repository.*;
import ch.dissem.bitmessage.wif.WifExporter; import ch.dissem.bitmessage.wif.WifExporter;
import ch.dissem.bitmessage.wif.WifImporter; import ch.dissem.bitmessage.wif.WifImporter;
import org.jetbrains.annotations.NotNull;
import org.kohsuke.args4j.CmdLineException; import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser; import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option; import org.kohsuke.args4j.Option;
@ -62,18 +63,35 @@ public class Main {
.inventory(new JdbcInventory(jdbcConfig)) .inventory(new JdbcInventory(jdbcConfig))
.messageRepo(new JdbcMessageRepository(jdbcConfig)) .messageRepo(new JdbcMessageRepository(jdbcConfig))
.powRepo(new JdbcProofOfWorkRepository(jdbcConfig)) .powRepo(new JdbcProofOfWorkRepository(jdbcConfig))
.labelRepo(new JdbcLabelRepository(jdbcConfig))
.networkHandler(new NioNetworkHandler()) .networkHandler(new NioNetworkHandler())
.cryptography(new BouncyCryptography()) .cryptography(new BouncyCryptography());
.port(48444); ctxBuilder.getPreferences().setPort(48444);
if (options.localPort != null) { if (options.localPort != null) {
ctxBuilder.nodeRegistry(new NodeRegistry() { ctxBuilder.nodeRegistry(new NodeRegistry() {
@Override
public void cleanup() {
// NO OP
}
@Override
public void remove(@NotNull NetworkAddress node) {
// NO OP
}
@Override
public void update(@NotNull NetworkAddress node) {
// NO OP
}
@Override @Override
public void clear() { public void clear() {
// NO OP // NO OP
} }
@NotNull
@Override @Override
public List<NetworkAddress> getKnownAddresses(int limit, long... streams) { public List<NetworkAddress> getKnownAddresses(int limit, @NotNull long... streams) {
return Arrays.stream(streams) return Arrays.stream(streams)
.mapToObj(s -> new NetworkAddress.Builder() .mapToObj(s -> new NetworkAddress.Builder()
.ipv4(127, 0, 0, 1) .ipv4(127, 0, 0, 1)
@ -83,7 +101,7 @@ public class Main {
} }
@Override @Override
public void offerAddresses(List<NetworkAddress> nodes) { public void offerAddresses(@NotNull List<NetworkAddress> nodes) {
LOG.info("Local node registry ignored offered addresses: " + nodes); LOG.info("Local node registry ignored offered addresses: " + nodes);
} }
}); });

View File

@ -1,218 +0,0 @@
/*
* Copyright 2016 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;
import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.networking.nio.NioNetworkHandler;
import ch.dissem.bitmessage.ports.DefaultLabeler;
import ch.dissem.bitmessage.ports.Labeler;
import ch.dissem.bitmessage.repository.*;
import ch.dissem.bitmessage.utils.TTL;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static ch.dissem.bitmessage.entity.payload.Pubkey.Feature.DOES_ACK;
import static ch.dissem.bitmessage.utils.UnixTime.MINUTE;
import static com.nhaarman.mockito_kotlin.MockitoKt.spy;
import static com.nhaarman.mockito_kotlin.MockitoKt.timeout;
import static com.nhaarman.mockito_kotlin.MockitoKt.verify;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.any;
/**
* @author Christian Basler
*/
public class SystemTest {
private static int port = 6000;
private BitmessageContext alice;
private BitmessageAddress aliceIdentity;
private Labeler aliceLabeler;
private BitmessageContext bob;
private TestListener bobListener;
private BitmessageAddress bobIdentity;
@Before
public void setUp() {
TTL.msg(5 * MINUTE);
TTL.getpubkey(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", "");
aliceLabeler = spy(new DebugLabeler("Alice"));
TestListener aliceListener = new TestListener();
alice = new BitmessageContext.Builder()
.addressRepo(new JdbcAddressRepository(aliceDB))
.inventory(new JdbcInventory(aliceDB))
.messageRepo(new JdbcMessageRepository(aliceDB))
.powRepo(new JdbcProofOfWorkRepository(aliceDB))
.port(alicePort)
.nodeRegistry(new TestNodeRegistry(bobPort))
.networkHandler(new NioNetworkHandler())
.cryptography(new BouncyCryptography())
.listener(aliceListener)
.labeler(aliceLabeler)
.build();
alice.startup();
aliceIdentity = alice.createIdentity(false, DOES_ACK);
}
{
JdbcConfig bobDB = new JdbcConfig("jdbc:h2:mem:bob;DB_CLOSE_DELAY=-1", "sa", "");
bobListener = new TestListener();
bob = new BitmessageContext.Builder()
.addressRepo(new JdbcAddressRepository(bobDB))
.inventory(new JdbcInventory(bobDB))
.messageRepo(new JdbcMessageRepository(bobDB))
.powRepo(new JdbcProofOfWorkRepository(bobDB))
.port(bobPort)
.nodeRegistry(new TestNodeRegistry(alicePort))
.networkHandler(new NioNetworkHandler())
.cryptography(new BouncyCryptography())
.listener(bobListener)
.labeler(new DebugLabeler("Bob"))
.build();
bob.startup();
bobIdentity = bob.createIdentity(false, DOES_ACK);
}
((DebugLabeler) alice.labeler()).init(aliceIdentity, bobIdentity);
((DebugLabeler) bob.labeler()).init(aliceIdentity, bobIdentity);
}
@After
public void tearDown() {
alice.shutdown();
bob.shutdown();
}
@Test(timeout = 120_000)
public void ensureAliceCanSendMessageToBob() throws Exception {
String originalMessage = UUID.randomUUID().toString();
alice.send(aliceIdentity, new BitmessageAddress(bobIdentity.getAddress()), "Subject", originalMessage);
Plaintext plaintext = bobListener.get(2, TimeUnit.MINUTES);
assertThat(plaintext.getType(), equalTo(Plaintext.Type.MSG));
assertThat(plaintext.getText(), equalTo(originalMessage));
verify(aliceLabeler, timeout(TimeUnit.MINUTES.toMillis(2)).atLeastOnce())
.markAsAcknowledged(any());
}
@Test(timeout = 30_000)
public void ensureBobCanReceiveBroadcastFromAlice() throws Exception {
String originalMessage = UUID.randomUUID().toString();
bob.addSubscribtion(new BitmessageAddress(aliceIdentity.getAddress()));
alice.broadcast(aliceIdentity, "Subject", originalMessage);
Plaintext plaintext = bobListener.get(15, TimeUnit.MINUTES);
assertThat(plaintext.getType(), equalTo(Plaintext.Type.BROADCAST));
assertThat(plaintext.getText(), equalTo(originalMessage));
}
private static class DebugLabeler extends DefaultLabeler {
private final Logger LOG = LoggerFactory.getLogger("Labeler");
final String name;
String alice;
String bob;
private DebugLabeler(String name) {
this.name = name;
}
private void init(BitmessageAddress alice, BitmessageAddress bob) {
this.alice = alice.getAddress();
this.bob = bob.getAddress();
}
@Override
public void setLabels(Plaintext msg) {
LOG.info(name + ": From " + name(msg.getFrom()) + ": Received");
super.setLabels(msg);
}
@Override
public void markAsDraft(Plaintext msg) {
LOG.info(name + ": From " + name(msg.getFrom()) + ": Draft");
super.markAsDraft(msg);
}
@Override
public void markAsSending(Plaintext msg) {
LOG.info(name + ": From " + name(msg.getFrom()) + ": Sending");
super.markAsSending(msg);
}
@Override
public void markAsSent(Plaintext msg) {
LOG.info(name + ": From " + name(msg.getFrom()) + ": Sent");
super.markAsSent(msg);
}
@Override
public void markAsAcknowledged(Plaintext msg) {
LOG.info(name + ": From " + name(msg.getFrom()) + ": Acknowledged");
super.markAsAcknowledged(msg);
}
@Override
public void markAsRead(Plaintext msg) {
LOG.info(name + ": From " + name(msg.getFrom()) + ": Read");
super.markAsRead(msg);
}
@Override
public void markAsUnread(Plaintext msg) {
LOG.info(name + ": From " + name(msg.getFrom()) + ": Unread");
super.markAsUnread(msg);
}
@Override
public void delete(Plaintext msg) {
LOG.info(name + ": From " + name(msg.getFrom()) + ": Cleared");
super.delete(msg);
}
@Override
public void archive(Plaintext msg) {
LOG.info(name + ": From " + name(msg.getFrom()) + ": Archived");
super.archive(msg);
}
private String name(BitmessageAddress address) {
if (alice.equals(address.getAddress()))
return "Alice";
else if (bob.equals(address.getAddress()))
return "Bob";
else
return "Unknown (" + address.getAddress() + ")";
}
}
}

View File

@ -0,0 +1,215 @@
/*
* Copyright 2016 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
import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography
import ch.dissem.bitmessage.entity.BitmessageAddress
import ch.dissem.bitmessage.entity.Plaintext
import ch.dissem.bitmessage.entity.payload.Pubkey.Feature.DOES_ACK
import ch.dissem.bitmessage.networking.nio.NioNetworkHandler
import ch.dissem.bitmessage.ports.DefaultLabeler
import ch.dissem.bitmessage.ports.Labeler
import ch.dissem.bitmessage.repository.*
import ch.dissem.bitmessage.utils.TTL
import ch.dissem.bitmessage.utils.UnixTime.MINUTE
import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.spy
import com.nhaarman.mockito_kotlin.timeout
import com.nhaarman.mockito_kotlin.verify
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTimeoutPreemptively
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.slf4j.LoggerFactory
import java.time.Duration.ofMinutes
import java.time.Duration.ofSeconds
import java.util.*
import java.util.concurrent.TimeUnit
/**
* @author Christian Basler
*/
class SystemTest {
private lateinit var alice: BitmessageContext
private lateinit var aliceIdentity: BitmessageAddress
private lateinit var aliceLabeler: Labeler
private lateinit var bob: BitmessageContext
private lateinit var bobListener: TestListener
private lateinit var bobIdentity: BitmessageAddress
@BeforeEach
fun setUp() {
TTL.msg = 5 * MINUTE
TTL.getpubkey = 5 * MINUTE
TTL.pubkey = 5 * MINUTE
val alicePort = port++
val bobPort = port++
run {
val aliceDB = JdbcConfig("jdbc:h2:mem:alice;DB_CLOSE_DELAY=-1", "sa", "")
aliceLabeler = spy(DebugLabeler("Alice"))
val aliceListener = TestListener()
alice = BitmessageContext.Builder()
.addressRepo(JdbcAddressRepository(aliceDB))
.inventory(JdbcInventory(aliceDB))
.labelRepo(JdbcLabelRepository(aliceDB))
.messageRepo(JdbcMessageRepository(aliceDB))
.powRepo(JdbcProofOfWorkRepository(aliceDB))
.nodeRegistry(TestNodeRegistry(bobPort))
.networkHandler(NioNetworkHandler())
.cryptography(BouncyCryptography())
.listener(aliceListener)
.labeler(aliceLabeler)
.build()
alice.internals.preferences.port = alicePort
alice.startup()
aliceIdentity = alice.createIdentity(false, DOES_ACK)
}
run {
val bobDB = JdbcConfig("jdbc:h2:mem:bob;DB_CLOSE_DELAY=-1", "sa", "")
bobListener = TestListener()
bob = BitmessageContext.Builder()
.addressRepo(JdbcAddressRepository(bobDB))
.inventory(JdbcInventory(bobDB))
.labelRepo(JdbcLabelRepository(bobDB))
.messageRepo(JdbcMessageRepository(bobDB))
.powRepo(JdbcProofOfWorkRepository(bobDB))
.nodeRegistry(TestNodeRegistry(alicePort))
.networkHandler(NioNetworkHandler())
.cryptography(BouncyCryptography())
.listener(bobListener)
.labeler(DebugLabeler("Bob"))
.build()
bob.internals.preferences.port = bobPort
bob.startup()
bobIdentity = bob.createIdentity(false, DOES_ACK)
}
(alice.labeler as DebugLabeler).init(aliceIdentity, bobIdentity)
(bob.labeler as DebugLabeler).init(aliceIdentity, bobIdentity)
}
@AfterEach
fun tearDown() {
alice.shutdown()
bob.shutdown()
}
@Test
@Throws(Exception::class)
fun ensureAliceCanSendMessageToBob() {
assertTimeoutPreemptively(ofMinutes(2)) {
val originalMessage = UUID.randomUUID().toString()
alice.send(aliceIdentity, BitmessageAddress(bobIdentity.address), "Subject", originalMessage)
val plaintext = bobListener[2, TimeUnit.MINUTES]
assertEquals(Plaintext.Type.MSG, plaintext.type)
assertEquals(originalMessage, plaintext.text)
verify(
aliceLabeler,
timeout(TimeUnit.MINUTES.toMillis(2)).atLeastOnce()
).markAsAcknowledged(any())
}
}
@Test
@Throws(Exception::class)
fun ensureBobCanReceiveBroadcastFromAlice() {
assertTimeoutPreemptively(ofSeconds(30)) {
val originalMessage = UUID.randomUUID().toString()
bob.addSubscribtion(BitmessageAddress(aliceIdentity.address))
alice.broadcast(aliceIdentity, "Subject", originalMessage)
val plaintext = bobListener[15, TimeUnit.MINUTES]
assertEquals(Plaintext.Type.BROADCAST, plaintext.type)
assertEquals(originalMessage, plaintext.text)
}
}
internal open class DebugLabeler internal constructor(private val name: String) : DefaultLabeler() {
private val LOG = LoggerFactory.getLogger("Labeler")
private lateinit var alice: String
private lateinit var bob: String
internal fun init(alice: BitmessageAddress, bob: BitmessageAddress) {
this.alice = alice.address
this.bob = bob.address
}
override fun setLabels(msg: Plaintext) {
LOG.info(name + ": From " + name(msg.from) + ": Received")
super.setLabels(msg)
}
override fun markAsDraft(msg: Plaintext) {
LOG.info(name + ": From " + name(msg.from) + ": Draft")
super.markAsDraft(msg)
}
override fun markAsSending(msg: Plaintext) {
LOG.info(name + ": From " + name(msg.from) + ": Sending")
super.markAsSending(msg)
}
override fun markAsSent(msg: Plaintext) {
LOG.info(name + ": From " + name(msg.from) + ": Sent")
super.markAsSent(msg)
}
override fun markAsAcknowledged(msg: Plaintext) {
LOG.info(name + ": From " + name(msg.from) + ": Acknowledged")
super.markAsAcknowledged(msg)
}
override fun markAsRead(msg: Plaintext) {
LOG.info(name + ": From " + name(msg.from) + ": Read")
super.markAsRead(msg)
}
override fun markAsUnread(msg: Plaintext) {
LOG.info(name + ": From " + name(msg.from) + ": Unread")
super.markAsUnread(msg)
}
override fun delete(msg: Plaintext) {
LOG.info(name + ": From " + name(msg.from) + ": Cleared")
super.delete(msg)
}
override fun archive(msg: Plaintext) {
LOG.info(name + ": From " + name(msg.from) + ": Archived")
super.archive(msg)
}
private fun name(address: BitmessageAddress): String {
return when {
alice == address.address -> "Alice"
bob == address.address -> "Bob"
else -> "Unknown (" + address.address + ")"
}
}
}
companion object {
private var port = 6000
}
}

View File

@ -18,6 +18,7 @@ package ch.dissem.bitmessage;
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
import ch.dissem.bitmessage.ports.NodeRegistry; import ch.dissem.bitmessage.ports.NodeRegistry;
import org.jetbrains.annotations.NotNull;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -44,13 +45,29 @@ class TestNodeRegistry implements NodeRegistry {
// NO OP // NO OP
} }
@NotNull
@Override @Override
public List<NetworkAddress> getKnownAddresses(int limit, long... streams) { public List<NetworkAddress> getKnownAddresses(int limit, @NotNull long... streams) {
return nodes; return nodes;
} }
@Override @Override
public void offerAddresses(List<NetworkAddress> nodes) { public void offerAddresses(@NotNull List<NetworkAddress> nodes) {
// Ignore
}
@Override
public void update(@NotNull NetworkAddress node) {
// Ignore
}
@Override
public void remove(@NotNull NetworkAddress node) {
// Ignore
}
@Override
public void cleanup() {
// Ignore // Ignore
} }
} }

View File

@ -15,9 +15,10 @@ dependencies {
compile 'org.slf4j:slf4j-api' compile 'org.slf4j:slf4j-api'
compile 'com.beust:klaxon' compile 'com.beust:klaxon'
testCompile 'junit:junit:4.12' testCompile 'com.nhaarman:mockito-kotlin'
testCompile 'org.hamcrest:hamcrest-library:1.3' testCompile 'org.junit.jupiter:junit-jupiter-api'
testCompile 'com.nhaarman:mockito-kotlin:1.5.0' testRuntime 'org.junit.jupiter:junit-jupiter-engine'
testCompile project(path: ':core', configuration: 'testArtifacts') testCompile project(path: ':core', configuration: 'testArtifacts')
testCompile project(':cryptography-bc') testCompile project(':cryptography-bc')
} }

View File

@ -39,7 +39,7 @@ object ContactExport {
"subscribed" to it.isSubscribed, "subscribed" to it.isSubscribed,
"pubkey" to it.pubkey?.let { "pubkey" to it.pubkey?.let {
val out = ByteArrayOutputStream() val out = ByteArrayOutputStream()
it.writeUnencrypted(out) it.writer().writeUnencrypted(out)
Base64.encodeToString(out.toByteArray()) Base64.encodeToString(out.toByteArray())
}, },
"privateKey" to if (includePrivateKey) { "privateKey" to if (includePrivateKey) {
@ -68,7 +68,7 @@ object ContactExport {
Factory.readPubkey( Factory.readPubkey(
version = version, version = version,
stream = stream, stream = stream,
`is` = ByteArrayInputStream(it), input = ByteArrayInputStream(it),
length = it.size, length = it.size,
encrypted = false encrypted = false
) )

View File

@ -19,10 +19,8 @@ package ch.dissem.bitmessage.exports
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.utils.TestUtils import ch.dissem.bitmessage.utils.TestUtils
import org.hamcrest.CoreMatchers.`is` import org.junit.jupiter.api.Assertions.*
import org.hamcrest.CoreMatchers.nullValue import org.junit.jupiter.api.Test
import org.junit.Assert.assertThat
import org.junit.Test
class ContactExportTest { class ContactExportTest {
@ -42,7 +40,7 @@ class ContactExportTest {
) )
val export = ContactExport.exportContacts(contacts) val export = ContactExport.exportContacts(contacts)
print(export.toJsonString(true)) print(export.toJsonString(true))
assertThat(ContactExport.importContacts(export), `is`(contacts)) assertEquals(contacts, ContactExport.importContacts(export))
} }
@Test @Test
@ -53,9 +51,9 @@ class ContactExportTest {
val export = ContactExport.exportContacts(contacts) val export = ContactExport.exportContacts(contacts)
print(export.toJsonString(true)) print(export.toJsonString(true))
val import = ContactExport.importContacts(export) val import = ContactExport.importContacts(export)
assertThat(import.size, `is`(1)) assertEquals(1, import.size)
assertThat(import[0].isChan, `is`(true)) assertTrue(import[0].isChan)
assertThat(import[0].privateKey, `is`(nullValue())) assertNull(import[0].privateKey)
} }
@Test @Test
@ -66,8 +64,9 @@ class ContactExportTest {
val export = ContactExport.exportContacts(contacts, true) val export = ContactExport.exportContacts(contacts, true)
print(export.toJsonString(true)) print(export.toJsonString(true))
val import = ContactExport.importContacts(export) val import = ContactExport.importContacts(export)
assertThat(import.size, `is`(1))
assertThat(import[0].isChan, `is`(true)) assertEquals(1, import.size)
assertThat(import[0].privateKey, `is`(contacts[0].privateKey)) assertTrue(import[0].isChan)
assertEquals(contacts[0].privateKey, import[0].privateKey)
} }
} }

View File

@ -23,23 +23,22 @@ import ch.dissem.bitmessage.entity.valueobject.Label
import ch.dissem.bitmessage.utils.ConversationServiceTest import ch.dissem.bitmessage.utils.ConversationServiceTest
import ch.dissem.bitmessage.utils.Singleton import ch.dissem.bitmessage.utils.Singleton
import ch.dissem.bitmessage.utils.TestUtils import ch.dissem.bitmessage.utils.TestUtils
import org.hamcrest.CoreMatchers.`is` import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.Assert.assertThat import org.junit.jupiter.api.Test
import org.junit.Test
class MessageExportTest { class MessageExportTest {
val inbox = Label("Inbox", Label.Type.INBOX, 0x0000ff) private val inbox = Label("Inbox", Label.Type.INBOX, 0x0000ff)
val outbox = Label("Outbox", Label.Type.OUTBOX, 0x00ff00) private val outbox = Label("Outbox", Label.Type.OUTBOX, 0x00ff00)
val unread = Label("Unread", Label.Type.UNREAD, 0x000000) private val unread = Label("Unread", Label.Type.UNREAD, 0x000000)
val trash = Label("Trash", Label.Type.TRASH, 0x555555) private val trash = Label("Trash", Label.Type.TRASH, 0x555555)
val labels = listOf( private val labels = listOf(
inbox, inbox,
outbox, outbox,
unread, unread,
trash trash
) )
val labelMap = MessageExport.createLabelMap(labels) private val labelMap = MessageExport.createLabelMap(labels)
init { init {
TestUtils.mockedInternalContext(cryptography = BouncyCryptography()) TestUtils.mockedInternalContext(cryptography = BouncyCryptography())
@ -49,7 +48,7 @@ class MessageExportTest {
fun `ensure labels are exported`() { fun `ensure labels are exported`() {
val export = MessageExport.exportLabels(labels) val export = MessageExport.exportLabels(labels)
print(export.toJsonString(true)) print(export.toJsonString(true))
assertThat(MessageExport.importLabels(export), `is`(labels)) assertEquals(labels, MessageExport.importLabels(export))
} }
@Test @Test
@ -84,6 +83,6 @@ class MessageExportTest {
) )
val export = MessageExport.exportMessages(messages) val export = MessageExport.exportMessages(messages)
print(export.toJsonString(true)) print(export.toJsonString(true))
assertThat(MessageExport.importMessages(export, labelMap), `is`(messages)) assertEquals(messages, MessageExport.importMessages(export, labelMap))
} }
} }

View File

@ -28,9 +28,10 @@ uploadArchives {
dependencies { dependencies {
compile project(':core') compile project(':core')
testCompile 'junit:junit'
testCompile 'org.slf4j:slf4j-simple' testCompile 'org.slf4j:slf4j-simple'
testCompile 'com.nhaarman:mockito-kotlin' testCompile 'com.nhaarman:mockito-kotlin'
testCompile 'org.junit.jupiter:junit-jupiter-api'
testRuntime 'org.junit.jupiter:junit-jupiter-engine'
testCompile project(path: ':core', configuration: 'testArtifacts') testCompile project(path: ':core', configuration: 'testArtifacts')
testCompile project(':cryptography-bc') testCompile project(':cryptography-bc')
} }

View File

@ -19,6 +19,7 @@ package ch.dissem.bitmessage.extensions
import ch.dissem.bitmessage.entity.BitmessageAddress import ch.dissem.bitmessage.entity.BitmessageAddress
import ch.dissem.bitmessage.entity.CustomMessage import ch.dissem.bitmessage.entity.CustomMessage
import ch.dissem.bitmessage.entity.Streamable import ch.dissem.bitmessage.entity.Streamable
import ch.dissem.bitmessage.entity.StreamableWriter
import ch.dissem.bitmessage.entity.payload.CryptoBox import ch.dissem.bitmessage.entity.payload.CryptoBox
import ch.dissem.bitmessage.entity.payload.Pubkey import ch.dissem.bitmessage.entity.payload.Pubkey
import ch.dissem.bitmessage.exception.DecryptionFailedException import ch.dissem.bitmessage.exception.DecryptionFailedException
@ -41,19 +42,21 @@ import java.io.OutputStream
*/ */
class CryptoCustomMessage<T : Streamable> : CustomMessage { class CryptoCustomMessage<T : Streamable> : CustomMessage {
private val dataReader: Reader<T>? private val dataReader: (BitmessageAddress, InputStream) -> T
private var container: CryptoBox? = null private var container: CryptoBox? = null
var sender: BitmessageAddress? = null var sender: BitmessageAddress? = null
private set private set
private var data: T? = null private var data: T? = null
private set
constructor(data: T) : super(COMMAND, null) { constructor(data: T) : super(COMMAND, null) {
this.data = data this.data = data
this.dataReader = null this.dataReader = { _, _ -> data }
} }
private constructor(container: CryptoBox, dataReader: Reader<T>) : super(COMMAND, null) { private constructor(container: CryptoBox, dataReader: (BitmessageAddress, InputStream) -> T) : super(
COMMAND,
null
) {
this.container = container this.container = container
this.dataReader = dataReader this.dataReader = dataReader
} }
@ -73,25 +76,27 @@ class CryptoCustomMessage<T : Streamable> : CustomMessage {
Encode.varInt(privateKey.pubkey.extraBytes, out) Encode.varInt(privateKey.pubkey.extraBytes, out)
} }
data?.write(out) ?: throw IllegalStateException("no unencrypted data available") data?.writer()?.write(out) ?: throw IllegalStateException("no unencrypted data available")
Encode.varBytes(cryptography().getSignature(out.toByteArray(), privateKey), out) Encode.varBytes(cryptography().getSignature(out.toByteArray(), privateKey), out)
container = CryptoBox(out.toByteArray(), publicKey) container = CryptoBox(out.toByteArray(), publicKey)
} }
@Throws(DecryptionFailedException::class) @Throws(DecryptionFailedException::class)
fun decrypt(privateKey: ByteArray): T { fun decrypt(privateKey: ByteArray): T {
val `in` = SignatureCheckingInputStream(container?.decrypt(privateKey) ?: throw IllegalStateException("no encrypted data available")) val input = SignatureCheckingInputStream(
if (dataReader == null) throw IllegalStateException("no data reader available") container?.decrypt(privateKey) ?: throw IllegalStateException("no encrypted data available")
)
val addressVersion = varInt(`in`) val addressVersion = varInt(input)
val stream = varInt(`in`) val stream = varInt(input)
val behaviorBitfield = int32(`in`) val behaviorBitfield = int32(input)
val publicSigningKey = bytes(`in`, 64) val publicSigningKey = bytes(input, 64)
val publicEncryptionKey = bytes(`in`, 64) val publicEncryptionKey = bytes(input, 64)
val nonceTrialsPerByte = if (addressVersion >= 3) varInt(`in`) else 0 val nonceTrialsPerByte = if (addressVersion >= 3) varInt(input) else 0
val extraBytes = if (addressVersion >= 3) varInt(`in`) else 0 val extraBytes = if (addressVersion >= 3) varInt(input) else 0
val sender = BitmessageAddress(Factory.createPubkey( val sender = BitmessageAddress(
Factory.createPubkey(
addressVersion, addressVersion,
stream, stream,
publicSigningKey, publicSigningKey,
@ -99,26 +104,36 @@ class CryptoCustomMessage<T : Streamable> : CustomMessage {
nonceTrialsPerByte, nonceTrialsPerByte,
extraBytes, extraBytes,
behaviorBitfield behaviorBitfield
)) )
)
this.sender = sender this.sender = sender
data = dataReader.read(sender, `in`) data = dataReader.invoke(sender, input)
`in`.checkSignature(sender.pubkey!!) input.checkSignature(sender.pubkey!!)
return data!! return data!!
} }
override fun writer(): StreamableWriter = Writer(this)
private class Writer(
private val item: CryptoCustomMessage<*>
) : CustomMessage.Writer(item) {
override fun write(out: OutputStream) { override fun write(out: OutputStream) {
Encode.varString(COMMAND, out) Encode.varString(COMMAND, out)
container?.write(out) ?: throw IllegalStateException("not encrypted yet") item.container?.writer()?.write(out) ?: throw IllegalStateException("not encrypted yet")
}
} }
interface Reader<out T> { interface Reader<out T> {
fun read(sender: BitmessageAddress, `in`: InputStream): T fun read(sender: BitmessageAddress, input: InputStream): T
} }
private inner class SignatureCheckingInputStream internal constructor(private val wrapped: InputStream) : InputStream() { private inner class SignatureCheckingInputStream internal constructor(private val wrapped: InputStream) :
InputStream() {
private val out = ByteArrayOutputStream() private val out = ByteArrayOutputStream()
override fun read(): Int { override fun read(): Int {
@ -136,11 +151,24 @@ class CryptoCustomMessage<T : Streamable> : CustomMessage {
} }
companion object { companion object {
@JvmField val COMMAND = "ENCRYPTED" const val COMMAND = "ENCRYPTED"
@JvmStatic fun <T : Streamable> read(data: CustomMessage, dataReader: Reader<T>): CryptoCustomMessage<T> { @JvmStatic
val cryptoBox = CryptoBox.read(ByteArrayInputStream(data.getData()), data.getData().size) fun <T : Streamable> read(data: CustomMessage, dataReader: Reader<T>): CryptoCustomMessage<T> =
return CryptoCustomMessage(cryptoBox, dataReader) CryptoCustomMessage(
} CryptoBox.read(ByteArrayInputStream(data.getData()), data.getData().size)
) { address, input ->
dataReader.read(address, input)
}
@JvmSynthetic
fun <T : Streamable> read(
data: CustomMessage,
dataReader: (BitmessageAddress, InputStream) -> T
): CryptoCustomMessage<T> =
CryptoCustomMessage(
CryptoBox.read(ByteArrayInputStream(data.getData()), data.getData().size),
dataReader
)
} }
} }

View File

@ -18,6 +18,7 @@ package ch.dissem.bitmessage.extensions.pow
import ch.dissem.bitmessage.entity.BitmessageAddress import ch.dissem.bitmessage.entity.BitmessageAddress
import ch.dissem.bitmessage.entity.Streamable import ch.dissem.bitmessage.entity.Streamable
import ch.dissem.bitmessage.entity.StreamableWriter
import ch.dissem.bitmessage.extensions.CryptoCustomMessage import ch.dissem.bitmessage.extensions.CryptoCustomMessage
import ch.dissem.bitmessage.utils.Decode.bytes import ch.dissem.bitmessage.utils.Decode.bytes
import ch.dissem.bitmessage.utils.Decode.varBytes import ch.dissem.bitmessage.utils.Decode.varBytes
@ -33,22 +34,30 @@ import java.util.*
*/ */
data class ProofOfWorkRequest @JvmOverloads constructor(val sender: BitmessageAddress, val initialHash: ByteArray, val request: ProofOfWorkRequest.Request, val data: ByteArray = ByteArray(0)) : Streamable { data class ProofOfWorkRequest @JvmOverloads constructor(val sender: BitmessageAddress, val initialHash: ByteArray, val request: ProofOfWorkRequest.Request, val data: ByteArray = ByteArray(0)) : Streamable {
override fun writer(): StreamableWriter = Writer(this)
private class Writer(
private val item: ProofOfWorkRequest
) : StreamableWriter {
override fun write(out: OutputStream) { override fun write(out: OutputStream) {
out.write(initialHash) out.write(item.initialHash)
Encode.varString(request.name, out) Encode.varString(item.request.name, out)
Encode.varBytes(data, out) Encode.varBytes(item.data, out)
} }
override fun write(buffer: ByteBuffer) { override fun write(buffer: ByteBuffer) {
buffer.put(initialHash) buffer.put(item.initialHash)
Encode.varString(request.name, buffer) Encode.varString(item.request.name, buffer)
Encode.varBytes(data, buffer) Encode.varBytes(item.data, buffer)
}
} }
class Reader(private val identity: BitmessageAddress) : CryptoCustomMessage.Reader<ProofOfWorkRequest> { class Reader(private val identity: BitmessageAddress) : CryptoCustomMessage.Reader<ProofOfWorkRequest> {
override fun read(sender: BitmessageAddress, `in`: InputStream): ProofOfWorkRequest { override fun read(sender: BitmessageAddress, input: InputStream): ProofOfWorkRequest {
return ProofOfWorkRequest.read(identity, `in`) return ProofOfWorkRequest.read(identity, input)
} }
} }
@ -78,12 +87,12 @@ data class ProofOfWorkRequest @JvmOverloads constructor(val sender: BitmessageAd
companion object { companion object {
@JvmStatic @JvmStatic
fun read(client: BitmessageAddress, `in`: InputStream): ProofOfWorkRequest { fun read(client: BitmessageAddress, input: InputStream): ProofOfWorkRequest {
return ProofOfWorkRequest( return ProofOfWorkRequest(
client, client,
bytes(`in`, 64), bytes(input, 64),
Request.valueOf(varString(`in`)), Request.valueOf(varString(input)),
varBytes(`in`) varBytes(input)
) )
} }
} }

View File

@ -21,17 +21,15 @@ import ch.dissem.bitmessage.entity.CustomMessage
import ch.dissem.bitmessage.entity.payload.GenericPayload import ch.dissem.bitmessage.entity.payload.GenericPayload
import ch.dissem.bitmessage.entity.valueobject.PrivateKey import ch.dissem.bitmessage.entity.valueobject.PrivateKey
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest
import ch.dissem.bitmessage.utils.Singleton.cryptography
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 org.junit.Test import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.InputStream import java.io.InputStream
import ch.dissem.bitmessage.utils.Singleton.cryptography
import org.junit.Assert.assertEquals
class CryptoCustomMessageTest : TestBase() { class CryptoCustomMessageTest : TestBase() {
@Test @Test
fun `ensure encrypt then decrypt yields same object`() { fun `ensure encrypt then decrypt yields same object`() {
@ -40,18 +38,20 @@ class CryptoCustomMessageTest : TestBase() {
val payloadBefore = GenericPayload(0, 1, cryptography().randomBytes(100)) val payloadBefore = GenericPayload(0, 1, cryptography().randomBytes(100))
val messageBefore = CryptoCustomMessage(payloadBefore) val messageBefore = CryptoCustomMessage(payloadBefore)
messageBefore.signAndEncrypt(sendingIdentity, cryptography().createPublicKey(sendingIdentity.publicDecryptionKey)) messageBefore.signAndEncrypt(
sendingIdentity,
cryptography().createPublicKey(sendingIdentity.publicDecryptionKey)
)
val out = ByteArrayOutputStream() val out = ByteArrayOutputStream()
messageBefore.write(out) messageBefore.writer().write(out)
val `in` = ByteArrayInputStream(out.toByteArray()) val input = ByteArrayInputStream(out.toByteArray())
val customMessage = CustomMessage.read(`in`, out.size()) val customMessage = CustomMessage.read(input, out.size())
val messageAfter = CryptoCustomMessage.read(customMessage, val messageAfter = CryptoCustomMessage.read(customMessage,
object : CryptoCustomMessage.Reader<GenericPayload> { object : CryptoCustomMessage.Reader<GenericPayload> {
override fun read(sender: BitmessageAddress, `in`: InputStream): GenericPayload { override fun read(sender: BitmessageAddress, input: InputStream) =
return GenericPayload.read(0, 1, `in`, 100) GenericPayload.read(0, 1, input, 100)
}
}) })
val payloadAfter = messageAfter.decrypt(sendingIdentity.publicDecryptionKey) val payloadAfter = messageAfter.decrypt(sendingIdentity.publicDecryptionKey)
@ -63,20 +63,27 @@ class CryptoCustomMessageTest : TestBase() {
val privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey")) val privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey"))
val sendingIdentity = BitmessageAddress(privateKey) val sendingIdentity = BitmessageAddress(privateKey)
val requestBefore = ProofOfWorkRequest(sendingIdentity, cryptography().randomBytes(64), val requestBefore = ProofOfWorkRequest(
ProofOfWorkRequest.Request.CALCULATE) sendingIdentity, cryptography().randomBytes(64),
ProofOfWorkRequest.Request.CALCULATE
)
val messageBefore = CryptoCustomMessage(requestBefore) val messageBefore = CryptoCustomMessage(requestBefore)
messageBefore.signAndEncrypt(sendingIdentity, cryptography().createPublicKey(sendingIdentity.publicDecryptionKey)) messageBefore.signAndEncrypt(
sendingIdentity,
cryptography().createPublicKey(sendingIdentity.publicDecryptionKey)
)
val out = ByteArrayOutputStream() val out = ByteArrayOutputStream()
messageBefore.write(out) messageBefore.writer().write(out)
val `in` = ByteArrayInputStream(out.toByteArray()) val input = ByteArrayInputStream(out.toByteArray())
val customMessage = CustomMessage.read(`in`, out.size()) val customMessage = CustomMessage.read(input, out.size())
val messageAfter = CryptoCustomMessage.read(customMessage, val messageAfter = CryptoCustomMessage.read(
ProofOfWorkRequest.Reader(sendingIdentity)) customMessage,
ProofOfWorkRequest.Reader(sendingIdentity)
)
val requestAfter = messageAfter.decrypt(sendingIdentity.publicDecryptionKey) val requestAfter = messageAfter.decrypt(sendingIdentity.publicDecryptionKey)
assertEquals(requestBefore, requestAfter) assertEquals(requestBefore, requestAfter)

Binary file not shown.

View File

@ -1,6 +1,6 @@
#Mon Jul 17 06:32:41 CEST 2017 #Sat Mar 03 14:35:52 CET 2018
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip

View File

@ -12,9 +12,10 @@ uploadArchives {
dependencies { dependencies {
compile project(':core') compile project(':core')
testCompile 'junit:junit'
testCompile 'org.slf4j:slf4j-simple' testCompile 'org.slf4j:slf4j-simple'
testCompile 'com.nhaarman:mockito-kotlin' testCompile 'com.nhaarman:mockito-kotlin'
testCompile 'org.junit.jupiter:junit-jupiter-api'
testRuntime 'org.junit.jupiter:junit-jupiter-engine'
testCompile project(path: ':core', configuration: 'testArtifacts') testCompile project(path: ':core', configuration: 'testArtifacts')
testCompile project(':cryptography-bc') testCompile project(':cryptography-bc')
} }

View File

@ -31,6 +31,7 @@ import org.slf4j.LoggerFactory
import java.io.IOException import java.io.IOException
import java.util.* import java.util.*
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit
/** /**
* Contains everything used by both the old streams-oriented NetworkHandler and the new NioNetworkHandler, * Contains everything used by both the old streams-oriented NetworkHandler and the new NioNetworkHandler,
@ -58,7 +59,7 @@ class Connection(
private var lastObjectTime: Long = 0 private var lastObjectTime: Long = 0
lateinit var streams: LongArray lateinit var streams: LongArray
protected set private set
@Volatile var state = State.CONNECTING @Volatile var state = State.CONNECTING
private set private set
@ -165,11 +166,12 @@ class Connection(
} }
} }
// the TCP timeout starts out at 20 seconds // According to the specification, the TCP timeout starts out at 20 seconds
// after verack messages are exchanged, the timeout is raised to 10 minutes // after verack messages are exchanged, the timeout is raised to 10 minutes
// Let's tweak these numbers a bit:
fun isExpired(): Boolean = when (state) { fun isExpired(): Boolean = when (state) {
State.CONNECTING -> io.lastUpdate < System.currentTimeMillis() - 20000 State.CONNECTING -> io.lastUpdate < System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(9)
State.ACTIVE -> io.lastUpdate < System.currentTimeMillis() - 600000 State.ACTIVE -> io.lastUpdate < System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(3)
State.DISCONNECTED -> true State.DISCONNECTED -> true
} }

View File

@ -16,11 +16,11 @@
package ch.dissem.bitmessage.networking.nio package ch.dissem.bitmessage.networking.nio
import ch.dissem.bitmessage.constants.Network.HEADER_SIZE
import ch.dissem.bitmessage.entity.GetData import ch.dissem.bitmessage.entity.GetData
import ch.dissem.bitmessage.entity.MessagePayload import ch.dissem.bitmessage.entity.MessagePayload
import ch.dissem.bitmessage.entity.NetworkMessage import ch.dissem.bitmessage.entity.NetworkMessage
import ch.dissem.bitmessage.entity.valueobject.InventoryVector import ch.dissem.bitmessage.entity.valueobject.InventoryVector
import ch.dissem.bitmessage.exception.NodeException
import ch.dissem.bitmessage.factory.V3MessageReader import ch.dissem.bitmessage.factory.V3MessageReader
import ch.dissem.bitmessage.utils.UnixTime import ch.dissem.bitmessage.utils.UnixTime
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@ -39,9 +39,9 @@ class ConnectionIO(
private val getState: () -> Connection.State, private val getState: () -> Connection.State,
private val handleMessage: (MessagePayload) -> Unit private val handleMessage: (MessagePayload) -> Unit
) { ) {
private val headerOut: ByteBuffer = ByteBuffer.allocate(24) private val headerOut: ByteBuffer = ByteBuffer.allocate(HEADER_SIZE)
private var payloadOut: ByteBuffer? = null private var payloadOut: ByteBuffer? = null
private var reader: V3MessageReader? = V3MessageReader() private val reader = V3MessageReader()
internal val sendingQueue: Deque<MessagePayload> = ConcurrentLinkedDeque<MessagePayload>() internal val sendingQueue: Deque<MessagePayload> = ConcurrentLinkedDeque<MessagePayload>()
internal var lastUpdate = System.currentTimeMillis() internal var lastUpdate = System.currentTimeMillis()
@ -54,14 +54,13 @@ class ConnectionIO(
headerOut.flip() headerOut.flip()
} }
val inBuffer: ByteBuffer val inBuffer: ByteBuffer = reader.buffer
get() = reader?.getActiveBuffer() ?: throw NodeException("Node is disconnected")
fun updateWriter() { fun updateWriter() {
if (!headerOut.hasRemaining() && !sendingQueue.isEmpty()) { if (!headerOut.hasRemaining() && !sendingQueue.isEmpty()) {
headerOut.clear() headerOut.clear()
val payload = sendingQueue.poll() val payload = sendingQueue.poll()
payloadOut = NetworkMessage(payload).writeHeaderAndGetPayloadBuffer(headerOut) payloadOut = NetworkMessage(payload).writer().writeHeaderAndGetPayloadBuffer(headerOut)
headerOut.flip() headerOut.flip()
lastUpdate = System.currentTimeMillis() lastUpdate = System.currentTimeMillis()
} }
@ -77,7 +76,7 @@ class ConnectionIO(
} }
fun updateReader() { fun updateReader() {
reader?.let { reader -> reader.let { reader ->
reader.update() reader.update()
if (!reader.getMessages().isEmpty()) { if (!reader.getMessages().isEmpty()) {
val iterator = reader.getMessages().iterator() val iterator = reader.getMessages().iterator()
@ -95,11 +94,11 @@ class ConnectionIO(
fun updateSyncStatus() { fun updateSyncStatus() {
if (!isSyncFinished) { if (!isSyncFinished) {
isSyncFinished = reader?.getMessages()?.isEmpty() ?: true && syncFinished(null) isSyncFinished = reader.getMessages().isEmpty() && syncFinished(null)
} }
} }
protected fun syncFinished(msg: NetworkMessage?): Boolean { private fun syncFinished(msg: NetworkMessage?): Boolean {
if (mode != Connection.Mode.SYNC) { if (mode != Connection.Mode.SYNC) {
return false return false
} }
@ -126,10 +125,6 @@ class ConnectionIO(
} }
fun disconnect() { fun disconnect() {
reader?.let {
it.cleanup()
reader = null
}
payloadOut = null payloadOut = null
} }
@ -150,7 +145,7 @@ class ConnectionIO(
|| headerOut.hasRemaining() || headerOut.hasRemaining()
|| payloadOut?.hasRemaining() ?: false || payloadOut?.hasRemaining() ?: false
fun nothingToSend() = sendingQueue.isEmpty() private fun nothingToSend() = sendingQueue.isEmpty()
companion object { companion object {
val LOG = LoggerFactory.getLogger(ConnectionIO::class.java) val LOG = LoggerFactory.getLogger(ConnectionIO::class.java)

View File

@ -41,7 +41,7 @@ class NetworkConnectionInitializer(
fun start() { fun start() {
if (mode == Connection.Mode.CLIENT || mode == Connection.Mode.SYNC) { if (mode == Connection.Mode.CLIENT || mode == Connection.Mode.SYNC) {
send(Version(nonce = ctx.clientNonce, addrFrom = NetworkAddress.ANY, addrRecv = node, userAgent = ctx.userAgent)) send(Version(nonce = ctx.clientNonce, addrFrom = NetworkAddress.ANY, addrRecv = node, userAgent = ctx.preferences.userAgent))
} }
} }
@ -77,12 +77,12 @@ class NetworkConnectionInitializer(
activateConnection() activateConnection()
} }
} else { } else {
throw NodeException("Received unsupported version " + version.version + ", disconnecting.") throw NodeException("Received unsupported version ${version.version}, disconnecting.")
} }
} }
private fun activateConnection() { private fun activateConnection() {
LOG.info("Successfully established connection with node " + node) LOG.info("Successfully established connection with node $node")
markActive(version.streams) markActive(version.streams)
node.time = UnixTime.now node.time = UnixTime.now
if (mode != Connection.Mode.SYNC) { if (mode != Connection.Mode.SYNC) {

View File

@ -34,6 +34,7 @@ import ch.dissem.bitmessage.utils.Property
import ch.dissem.bitmessage.utils.ThreadFactoryBuilder.Companion.pool import ch.dissem.bitmessage.utils.ThreadFactoryBuilder.Companion.pool
import ch.dissem.bitmessage.utils.UnixTime.now import ch.dissem.bitmessage.utils.UnixTime.now
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.Closeable
import java.io.IOException import java.io.IOException
import java.net.InetAddress import java.net.InetAddress
import java.net.InetSocketAddress import java.net.InetSocketAddress
@ -47,20 +48,22 @@ import java.util.concurrent.*
/** /**
* Network handler using java.nio, resulting in less threads. * Network handler using java.nio, resulting in less threads.
*/ */
class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder { class NioNetworkHandler(private val magicNetworkNumber: Int = NETWORK_MAGIC_NUMBER) : NetworkHandler,
InternalContext.ContextHolder {
private val threadPool = Executors.newCachedThreadPool( private val threadPool = Executors.newCachedThreadPool(
pool("network") pool("network")
.lowPrio() .lowPrio()
.daemon() .daemon()
.build()) .build()
)
private lateinit var ctx: InternalContext private lateinit var ctx: InternalContext
private var selector: Selector? = null private var selector: Selector? = null
private var serverChannel: ServerSocketChannel? = null private var serverChannel: ServerSocketChannel? = null
private val connectionQueue = ConcurrentLinkedQueue<NetworkAddress>() private val connectionQueue = ConcurrentLinkedQueue<NetworkAddress>()
private val connections = ConcurrentHashMap<Connection, SelectionKey>() private val connections: MutableMap<Connection, SelectionKey> = ConcurrentHashMap()
private val requestedObjects = ConcurrentHashMap<InventoryVector, Long>(10000) private val requestedObjects: MutableMap<InventoryVector, Long> = ConcurrentHashMap(10000)
private var starter: Thread? = null private var starter: Thread? = null
@ -72,9 +75,11 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
return threadPool.submit(Callable<Void> { return threadPool.submit(Callable<Void> {
SocketChannel.open(InetSocketAddress(server, port)).use { channel -> SocketChannel.open(InetSocketAddress(server, port)).use { channel ->
channel.configureBlocking(false) channel.configureBlocking(false)
val connection = Connection(ctx, SYNC, val connection = Connection(
ctx, SYNC,
NetworkAddress.Builder().ip(server).port(port).stream(1).build(), NetworkAddress.Builder().ip(server).port(port).stream(1).build(),
HashMap<InventoryVector, Long>(), timeoutInSeconds) HashMap(), timeoutInSeconds
)
while (channel.isConnected && !connection.isSyncFinished) { while (channel.isConnected && !connection.isSyncFinished) {
write(channel, connection.io) write(channel, connection.io)
read(channel, connection.io) read(channel, connection.io)
@ -90,7 +95,7 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
SocketChannel.open(InetSocketAddress(server, port)).use { channel -> SocketChannel.open(InetSocketAddress(server, port)).use { channel ->
channel.configureBlocking(true) channel.configureBlocking(true)
val headerBuffer = ByteBuffer.allocate(HEADER_SIZE) val headerBuffer = ByteBuffer.allocate(HEADER_SIZE)
val payloadBuffer = NetworkMessage(request).writeHeaderAndGetPayloadBuffer(headerBuffer) val payloadBuffer = NetworkMessage(request).writer().writeHeaderAndGetPayloadBuffer(headerBuffer)
headerBuffer.flip() headerBuffer.flip()
while (headerBuffer.hasRemaining()) { while (headerBuffer.hasRemaining()) {
channel.write(headerBuffer) channel.write(headerBuffer)
@ -101,7 +106,7 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
val reader = V3MessageReader() val reader = V3MessageReader()
while (channel.isConnected && reader.getMessages().isEmpty()) { while (channel.isConnected && reader.getMessages().isEmpty()) {
if (channel.read(reader.getActiveBuffer()) > 0) { if (channel.read(reader.buffer) > 0) {
reader.update() reader.update()
} else { } else {
throw NodeException("No response from node $server") throw NodeException("No response from node $server")
@ -109,7 +114,7 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
} }
val networkMessage: NetworkMessage? val networkMessage: NetworkMessage?
if (reader.getMessages().isEmpty()) { if (reader.getMessages().isEmpty()) {
throw NodeException("No response from node " + server) throw NodeException("No response from node $server")
} else { } else {
networkMessage = reader.getMessages().first() networkMessage = reader.getMessages().first()
} }
@ -123,7 +128,7 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
} }
override fun start() { override fun start() {
if (selector?.isOpen ?: false) { if (selector?.isOpen == true) {
throw IllegalStateException("Network already running - you need to stop first.") throw IllegalStateException("Network already running - you need to stop first.")
} }
val selector = Selector.open() val selector = Selector.open()
@ -133,7 +138,7 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
starter = thread("connection manager") { starter = thread("connection manager") {
while (selector.isOpen) { while (selector.isOpen) {
var missing = NETWORK_MAGIC_NUMBER var missing = magicNetworkNumber
for ((connection, _) in connections) { for ((connection, _) in connections) {
if (connection.state == Connection.State.ACTIVE) { if (connection.state == Connection.State.ACTIVE) {
missing-- missing--
@ -187,19 +192,19 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
request(delayed) request(delayed)
try { try {
Thread.sleep(30000) Thread.sleep(10000)
} catch (e: InterruptedException) { } catch (e: InterruptedException) {
return@thread return@thread
} }
} }
} }
thread("selector worker", { thread("selector worker") {
try { try {
val serverChannel = ServerSocketChannel.open() val serverChannel = ServerSocketChannel.open()
this.serverChannel = serverChannel this.serverChannel = serverChannel
serverChannel.configureBlocking(false) serverChannel.configureBlocking(false)
serverChannel.socket().bind(InetSocketAddress(ctx.port)) serverChannel.socket().bind(InetSocketAddress(ctx.preferences.port))
serverChannel.register(selector, OP_ACCEPT, null) serverChannel.register(selector, OP_ACCEPT, null)
while (selector.isOpen) { while (selector.isOpen) {
@ -216,7 +221,8 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
try { try {
val accepted = (key.channel() as ServerSocketChannel).accept() val accepted = (key.channel() as ServerSocketChannel).accept()
accepted.configureBlocking(false) accepted.configureBlocking(false)
val connection = Connection(ctx, SERVER, val connection = Connection(
ctx, SERVER,
NetworkAddress( NetworkAddress(
time = now, time = now,
stream = 1L, stream = 1L,
@ -224,10 +230,8 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
), ),
requestedObjects, 0 requestedObjects, 0
) )
connections.put( connections[connection] =
connection,
accepted.register(selector, OP_READ or OP_WRITE, connection) accepted.register(selector, OP_READ or OP_WRITE, connection)
)
} catch (e: AsynchronousCloseException) { } catch (e: AsynchronousCloseException) {
LOG.trace(e.message) LOG.trace(e.message)
} catch (e: IOException) { } catch (e: IOException) {
@ -255,19 +259,22 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
if (key.isReadable) { if (key.isReadable) {
read(channel, connection.io) read(channel, connection.io)
} }
if (connection.state == Connection.State.DISCONNECTED) { when {
connection.state == Connection.State.DISCONNECTED -> {
key.interestOps(0) key.interestOps(0)
channel.close() channel.close()
} else if (connection.io.isWritePending) { }
key.interestOps(OP_READ or OP_WRITE) connection.io.isWritePending -> key.interestOps(OP_READ or OP_WRITE)
} else { else -> key.interestOps(OP_READ)
key.interestOps(OP_READ)
} }
} catch (e: CancelledKeyException) { } catch (e: CancelledKeyException) {
LOG.debug("${e.message}: ${connection.node}", e)
connection.disconnect() connection.disconnect()
} catch (e: NodeException) { } catch (e: NodeException) {
LOG.debug("${e.message}: ${connection.node}", e)
connection.disconnect() connection.disconnect()
} catch (e: IOException) { } catch (e: IOException) {
LOG.debug("${e.message}: ${connection.node}", e)
connection.disconnect() connection.disconnect()
} }
} }
@ -281,7 +288,8 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
if (selectionKey.isValid if (selectionKey.isValid
&& selectionKey.interestOps() and OP_WRITE == 0 && selectionKey.interestOps() and OP_WRITE == 0
&& selectionKey.interestOps() and OP_CONNECT == 0 && selectionKey.interestOps() and OP_CONNECT == 0
&& !connection.nothingToSend) { && !connection.nothingToSend
) {
selectionKey.interestOps(OP_READ or OP_WRITE) selectionKey.interestOps(OP_READ or OP_WRITE)
} }
} catch (x: CancelledKeyException) { } catch (x: CancelledKeyException) {
@ -297,10 +305,11 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
channel.configureBlocking(false) channel.configureBlocking(false)
channel.connect(InetSocketAddress(address.toInetAddress(), address.port)) channel.connect(InetSocketAddress(address.toInetAddress(), address.port))
val connection = Connection(ctx, CLIENT, address, requestedObjects, 0) val connection = Connection(ctx, CLIENT, address, requestedObjects, 0)
connections.put(
connection, connections[connection] = channel.register(selector, OP_CONNECT, connection)
channel.register(selector, OP_CONNECT, connection)
) LOG.debug("Connection registered to $address")
} catch (ignore: NoRouteToHostException) { } catch (ignore: NoRouteToHostException) {
// We'll try to connect to many offline nodes, so // We'll try to connect to many offline nodes, so
// this is expected to happen quite a lot. // this is expected to happen quite a lot.
@ -312,7 +321,11 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
LOG.error(e.message, e) LOG.error(e.message, e)
} }
} catch (e: IOException) { } catch (e: IOException) {
LOG.error(e.message, e) if (e.message == "Network is unreachable") {
LOG.debug("Network is unreachable: $address")
} else {
LOG.error("${e.message}: $address", e)
}
} }
} }
@ -326,7 +339,7 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
// isn't nice though. // isn't nice though.
LOG.error(e.message, e) LOG.error(e.message, e)
} }
}) }
} }
private fun thread(threadName: String, runnable: () -> Unit): Thread { private fun thread(threadName: String, runnable: () -> Unit): Thread {
@ -338,16 +351,24 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
} }
override fun stop() { override fun stop() {
serverChannel?.socket()?.close() tryClose(serverChannel?.socket())
selector?.close() tryClose(selector)
for (selectionKey in connections.values) { for (selectionKey in connections.values) {
selectionKey.channel().close() tryClose(selectionKey.channel())
}
}
private fun tryClose(item: Closeable?) {
try {
item?.close()
} catch (e: IOException) {
LOG.debug(e.message, e)
} }
} }
override fun offer(iv: InventoryVector) { override fun offer(iv: InventoryVector) {
val targetConnections = connections.keys.filter { it.state == Connection.State.ACTIVE && !it.knowsOf(iv) } val targetConnections = connections.keys.filter { it.state == Connection.State.ACTIVE && !it.knowsOf(iv) }
selectRandom(NETWORK_MAGIC_NUMBER, targetConnections).forEach { it.offer(iv) } selectRandom(magicNetworkNumber, targetConnections).forEach { it.offer(iv) }
} }
override fun request(inventoryVectors: MutableCollection<InventoryVector>) { override fun request(inventoryVectors: MutableCollection<InventoryVector>) {
@ -363,7 +384,7 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
val distribution = HashMap<Connection, MutableList<InventoryVector>>() val distribution = HashMap<Connection, MutableList<InventoryVector>>()
for ((connection, _) in connections) { for ((connection, _) in connections) {
if (connection.state == Connection.State.ACTIVE) { if (connection.state == Connection.State.ACTIVE) {
distribution.put(connection, mutableListOf<InventoryVector>()) distribution[connection] = mutableListOf()
} }
} }
if (distribution.isEmpty()) { if (distribution.isEmpty()) {
@ -382,7 +403,8 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
} }
} }
if (connection.knowsOf(next) && !connection.requested(next)) { if (connection.knowsOf(next) && !connection.requested(next)) {
val ivs = distribution[connection] ?: throw IllegalStateException("distribution not available for $connection") val ivs = distribution[connection]
?: throw IllegalStateException("distribution not available for $connection")
if (ivs.size == GetData.MAX_INVENTORY_SIZE) { if (ivs.size == GetData.MAX_INVENTORY_SIZE) {
connection.send(GetData(ivs)) connection.send(GetData(ivs))
ivs.clear() ivs.clear()
@ -406,7 +428,8 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
} }
for (connection in distribution.keys) { for (connection in distribution.keys) {
val ivs = distribution[connection] ?: throw IllegalStateException("distribution not available for $connection") val ivs =
distribution[connection] ?: throw IllegalStateException("distribution not available for $connection")
if (!ivs.isEmpty()) { if (!ivs.isEmpty()) {
connection.send(GetData(ivs)) connection.send(GetData(ivs))
} }
@ -434,12 +457,16 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
for (stream in streams) { for (stream in streams) {
val incoming = incomingConnections[stream] ?: 0 val incoming = incomingConnections[stream] ?: 0
val outgoing = outgoingConnections[stream] ?: 0 val outgoing = outgoingConnections[stream] ?: 0
streamProperties.add(Property("stream " + stream, Property("nodes", incoming + outgoing), streamProperties.add(
Property(
"stream $stream", Property("nodes", incoming + outgoing),
Property("incoming", incoming), Property("incoming", incoming),
Property("outgoing", outgoing) Property("outgoing", outgoing)
)) )
)
} }
return Property("network", return Property(
"network",
Property("connectionManager", if (isRunning) "running" else "stopped"), Property("connectionManager", if (isRunning) "running" else "stopped"),
Property("connections", streamProperties), Property("connections", streamProperties),
Property("requestedObjects", requestedObjects.size) Property("requestedObjects", requestedObjects.size)
@ -453,8 +480,8 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
companion object { companion object {
private val LOG = LoggerFactory.getLogger(NioNetworkHandler::class.java) private val LOG = LoggerFactory.getLogger(NioNetworkHandler::class.java)
private val REQUESTED_OBJECTS_MAX_TIME = (2 * 60000).toLong() // 2 minutes in ms private const val REQUESTED_OBJECTS_MAX_TIME = 2 * 60000L // 2 minutes in ms
private val DELAYED = java.lang.Long.MIN_VALUE private const val DELAYED = java.lang.Long.MIN_VALUE
private fun write(channel: SocketChannel, connection: ConnectionIO) { private fun write(channel: SocketChannel, connection: ConnectionIO) {
writeBuffer(connection.outBuffers, channel) writeBuffer(connection.outBuffers, channel)

Some files were not shown because too many files have changed in this diff Show More