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:
# IntelliJ
/out/
out/
# mpeltonen/sbt-idea plugin
.idea_modules/

View File

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

View File

@ -16,6 +16,7 @@
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_NONCE_TRIALS_PER_BYTE
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.ports.*
import ch.dissem.bitmessage.utils.Property
import ch.dissem.bitmessage.utils.UnixTime.HOUR
import ch.dissem.bitmessage.utils.UnixTime.MINUTE
import org.slf4j.LoggerFactory
import java.net.InetAddress
@ -58,57 +58,7 @@ import kotlin.properties.Delegates
*
* The port defaults to 8444 (the default Bitmessage port)
*/
class BitmessageContext(
cryptography: Cryptography,
inventory: Inventory,
nodeRegistry: NodeRegistry,
networkHandler: NetworkHandler,
addressRepository: AddressRepository,
messageRepository: MessageRepository,
proofOfWorkRepository: ProofOfWorkRepository,
proofOfWorkEngine: ProofOfWorkEngine = MultiThreadedPOWEngine(),
customCommandHandler: CustomCommandHandler = object : CustomCommandHandler {
override fun handle(request: CustomMessage): MessagePayload? {
BitmessageContext.LOG.debug("Received custom request, but no custom command handler configured.")
return null
}
},
listener: Listener,
labeler: Labeler = DefaultLabeler(),
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
class BitmessageContext private constructor(builder: BitmessageContext.Builder) {
/**
* The [InternalContext] - normally you wouldn't need it,
@ -123,6 +73,9 @@ class BitmessageContext(
val addresses: AddressRepository
@JvmName("addresses") get
val labels: LabelRepository
@JvmName("labels") get
val messages: MessageRepository
@JvmName("messages") get
@ -135,7 +88,7 @@ class BitmessageContext(
*features
))
internals.addressRepository.save(identity)
if (sendPubkeyOnIdentityCreation) {
if (internals.preferences.sendPubkeyOnIdentityCreation) {
internals.sendPubkey(identity, identity.stream)
}
return identity
@ -262,9 +215,8 @@ class BitmessageContext(
* @param request the request
* @return the response
*/
fun send(server: InetAddress, port: Int, request: CustomMessage): CustomMessage {
return internals.networkHandler.send(server, port, request)
}
fun send(server: InetAddress, port: Int, request: CustomMessage): CustomMessage =
internals.networkHandler.send(server, port, request)
/**
* Removes expired objects from the inventory. You should call this method regularly,
@ -272,6 +224,7 @@ class BitmessageContext(
*/
fun cleanup() {
internals.inventory.cleanup()
internals.nodeRegistry.cleanup()
}
/**
@ -326,7 +279,7 @@ class BitmessageContext(
fun status(): Property {
return Property("status",
Property("user agent", internals.userAgent),
Property("user agent", internals.preferences.userAgent),
internals.networkHandler.getNetworkStatus(),
Property("unacknowledged", internals.messageRepository.findMessagesToResend().size)
)
@ -343,29 +296,23 @@ class BitmessageContext(
}
}
/**
* Kotlin users: you might want to use [BitmessageContext.build] instead.
*/
class Builder {
internal var port = 8444
internal var inventory by Delegates.notNull<Inventory>()
internal var nodeRegistry by Delegates.notNull<NodeRegistry>()
internal var networkHandler by Delegates.notNull<NetworkHandler>()
internal var addressRepo by Delegates.notNull<AddressRepository>()
internal var messageRepo by Delegates.notNull<MessageRepository>()
internal var proofOfWorkRepository by Delegates.notNull<ProofOfWorkRepository>()
internal var proofOfWorkEngine: ProofOfWorkEngine? = null
internal var cryptography by Delegates.notNull<Cryptography>()
internal var customCommandHandler: CustomCommandHandler? = null
internal var labeler: Labeler? = null
internal var userAgent: String? = null
internal var listener by Delegates.notNull<Listener>()
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
}
var inventory by Delegates.notNull<Inventory>()
var nodeRegistry by Delegates.notNull<NodeRegistry>()
var networkHandler by Delegates.notNull<NetworkHandler>()
var addressRepo by Delegates.notNull<AddressRepository>()
var labelRepo by Delegates.notNull<LabelRepository>()
var messageRepo by Delegates.notNull<MessageRepository>()
var proofOfWorkRepo by Delegates.notNull<ProofOfWorkRepository>()
var proofOfWorkEngine: ProofOfWorkEngine? = null
var cryptography by Delegates.notNull<Cryptography>()
var customCommandHandler: CustomCommandHandler? = null
var labeler: Labeler? = null
var listener by Delegates.notNull<Listener>()
val preferences = Preferences()
fun inventory(inventory: Inventory): Builder {
this.inventory = inventory
@ -387,13 +334,18 @@ class BitmessageContext(
return this
}
fun labelRepo(labelRepo: LabelRepository): Builder {
this.labelRepo = labelRepo
return this
}
fun messageRepo(messageRepo: MessageRepository): Builder {
this.messageRepo = messageRepo
return this
}
fun powRepo(proofOfWorkRepository: ProofOfWorkRepository): Builder {
this.proofOfWorkRepository = proofOfWorkRepository
this.proofOfWorkRepo = proofOfWorkRepository
return this
}
@ -422,7 +374,7 @@ class BitmessageContext(
return this
}
@JvmName("kotlinListener")
@JvmSynthetic
fun listener(listener: (Plaintext) -> Unit): Builder {
this.listener = object : Listener {
override fun receive(plaintext: Plaintext) {
@ -432,63 +384,41 @@ class BitmessageContext(
return this
}
fun connectionLimit(connectionLimit: Int): Builder {
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)
}
fun build() = BitmessageContext(this)
}
init {
this.labeler = labeler
this.labeler = builder.labeler ?: DefaultLabeler()
this.internals = InternalContext(
cryptography,
inventory,
nodeRegistry,
networkHandler,
addressRepository,
messageRepository,
proofOfWorkRepository,
proofOfWorkEngine,
customCommandHandler,
listener,
builder.cryptography,
builder.inventory,
builder.nodeRegistry,
builder.networkHandler,
builder.addressRepo,
builder.labelRepo,
builder.messageRepo,
builder.proofOfWorkRepo,
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,
labeler,
userAgent?.let { "/$it/Jabit:$version/" } ?: "/Jabit:$version/",
port,
connectionTTL,
connectionLimit
builder.preferences
)
this.addresses = addressRepository
this.messages = messageRepository
this.sendPubkeyOnIdentityCreation = sendPubkeyOnIdentityCreation
(listener as? Listener.WithContext)?.setContext(this)
internals.proofOfWorkService.doMissingProofOfWork(doMissingProofOfWorkDelayInSeconds * 1000L)
this.addresses = builder.addressRepo
this.labels = builder.labelRepo
this.messages = builder.messageRepo
(builder.listener as? Listener.WithContext)?.setContext(this)
internals.proofOfWorkService.doMissingProofOfWork(builder.preferences.doMissingProofOfWorkDelayInSeconds * 1000L)
}
companion object {
@JvmField val CURRENT_VERSION = 3
@JvmField
val CURRENT_VERSION = 3
private val LOG = LoggerFactory.getLogger(BitmessageContext::class.java)
val version: String by lazy {
@ -496,5 +426,40 @@ class BitmessageContext(
}
@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(
val cryptography: Cryptography,
val inventory: ch.dissem.bitmessage.ports.Inventory,
val inventory: Inventory,
val nodeRegistry: NodeRegistry,
val networkHandler: NetworkHandler,
val addressRepository: AddressRepository,
val messageRepository: ch.dissem.bitmessage.ports.MessageRepository,
val labelRepository: LabelRepository,
val messageRepository: MessageRepository,
val proofOfWorkRepository: ProofOfWorkRepository,
val proofOfWorkEngine: ProofOfWorkEngine,
val customCommandHandler: CustomCommandHandler,
listener: BitmessageContext.Listener,
val labeler: Labeler,
val userAgent: String,
val port: Int,
val connectionTTL: Long,
val connectionLimit: Int
val preferences: Preferences
) {
private val threadPool = Executors.newCachedThreadPool()
@ -220,7 +217,9 @@ class InternalContext(
companion object {
private val LOG = LoggerFactory.getLogger(InternalContext::class.java)
@JvmField val NETWORK_NONCE_TRIALS_PER_BYTE: Long = 1000
@JvmField val NETWORK_EXTRA_BYTES: Long = 1000
@JvmField
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.
*/
data class Addr constructor(val addresses: List<NetworkAddress>) : MessagePayload {
override val command: MessagePayload.Command = MessagePayload.Command.ADDR
override fun write(out: OutputStream) {
Encode.varInt(addresses.size, out)
for (address in addresses) {
address.write(out)
}
}
override fun writer(): StreamableWriter = Writer(this)
override fun write(buffer: ByteBuffer) {
Encode.varInt(addresses.size, buffer)
for (address in addresses) {
address.write(buffer)
private class Writer(
private val item: Addr
) : StreamableWriter {
override fun write(out: OutputStream) {
Encode.varInt(item.addresses.size, out)
for (address in item.addresses) {
address.writer().write(out)
}
}
override fun write(buffer: ByteBuffer) {
Encode.varInt(item.addresses.size, buffer)
for (address in item.addresses) {
address.writer().write(buffer)
}
}
}
}

View File

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

View File

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

View File

@ -17,9 +17,6 @@
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
/**
* 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 fun write(out: OutputStream) {
Encode.varInt(inventory.size, out)
for (iv in inventory) {
iv.write(out)
}
}
override fun writer(): StreamableWriter = Writer(this)
override fun write(buffer: ByteBuffer) {
Encode.varInt(inventory.size, buffer)
for (iv in inventory) {
iv.write(buffer)
}
}
private class Writer(
item: GetData
) : InventoryWriter(item.inventory)
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
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.
@ -28,17 +25,9 @@ class Inv constructor(val inventory: List<InventoryVector>) : MessagePayload {
override val command: MessagePayload.Command = MessagePayload.Command.INV
override fun write(out: OutputStream) {
Encode.varInt(inventory.size, out)
for (iv in inventory) {
iv.write(out)
}
}
override fun writer(): StreamableWriter = Writer(this)
override fun write(buffer: ByteBuffer) {
Encode.varInt(inventory.size, buffer)
for (iv in inventory) {
iv.write(buffer)
}
}
private class Writer(
item: Inv
) : InventoryWriter(item.inventory)
}

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.Singleton.cryptography
import java.io.IOException
import java.io.OutputStream
import java.nio.ByteBuffer
@ -32,87 +31,95 @@ data class NetworkMessage(
val payload: MessagePayload
) : Streamable {
/**
* 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])
}
override fun writer(): Writer = Writer(this)
override fun write(out: OutputStream) {
// magic
Encode.int32(MAGIC, out)
class Writer internal constructor(
private val item: NetworkMessage
) : StreamableWriter {
// ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected)
val command = payload.command.name.toLowerCase()
out.write(command.toByteArray(charset("ASCII")))
for (i in command.length..11) {
out.write(0x0)
override fun write(out: OutputStream) {
// magic
Encode.int32(MAGIC, out)
// ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected)
val command = item.payload.command.name.toLowerCase()
out.write(command.toByteArray(charset("ASCII")))
for (i in command.length..11) {
out.write(0x0)
}
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
// ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are
// larger than this.
Encode.int32(payloadBytes.size, out)
// checksum
out.write(getChecksum(payloadBytes))
// message payload
out.write(payloadBytes)
}
val payloadBytes = Encode.bytes(payload)
/**
* A more efficient implementation of the write method, writing header data to the provided buffer and returning
* a new buffer containing the payload.
// Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would
// ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are
// larger than this.
Encode.int32(payloadBytes.size, out)
// checksum
out.write(getChecksum(payloadBytes))
// message payload
out.write(payloadBytes)
}
/**
* A more efficient implementation of the write method, writing header data to the provided buffer and returning
* a new buffer containing the payload.
* @param headerBuffer where the header data is written to (24 bytes)
* *
* @return a buffer containing the payload, ready to be read.
*/
fun writeHeaderAndGetPayloadBuffer(headerBuffer: ByteBuffer): ByteBuffer {
return ByteBuffer.wrap(writeHeader(headerBuffer))
}
/**
* For improved memory efficiency, you should use [.writeHeaderAndGetPayloadBuffer]
* and write the header buffer as well as the returned payload buffer into the channel.
* @param buffer where everything gets written to. Needs to be large enough for the whole message
* * to be written.
*/
override fun write(buffer: ByteBuffer) {
val payloadBytes = writeHeader(buffer)
buffer.put(payloadBytes)
}
private fun writeHeader(out: ByteBuffer): ByteArray {
// magic
Encode.int32(MAGIC, out)
// ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected)
val command = payload.command.name.toLowerCase()
out.put(command.toByteArray(charset("ASCII")))
for (i in command.length..11) {
out.put(0.toByte())
* @param headerBuffer where the header data is written to (24 bytes)
* *
* @return a buffer containing the payload, ready to be read.
*/
fun writeHeaderAndGetPayloadBuffer(headerBuffer: ByteBuffer): ByteBuffer {
return ByteBuffer.wrap(writeHeader(headerBuffer))
}
val payloadBytes = Encode.bytes(payload)
/**
* For improved memory efficiency, you should use [.writeHeaderAndGetPayloadBuffer]
* and write the header buffer as well as the returned payload buffer into the channel.
// Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would
// ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are
// larger than this.
Encode.int32(payloadBytes.size, out)
* @param buffer where everything gets written to. Needs to be large enough for the whole message
* * to be written.
*/
override fun write(buffer: ByteBuffer) {
val payloadBytes = writeHeader(buffer)
buffer.put(payloadBytes)
}
// checksum
out.put(getChecksum(payloadBytes))
private fun writeHeader(out: ByteBuffer): ByteArray {
// magic
Encode.int32(MAGIC, out)
// ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected)
val command = item.payload.command.name.toLowerCase()
out.put(command.toByteArray(charset("ASCII")))
for (i in command.length..11) {
out.put(0.toByte())
}
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
// ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are
// larger than this.
Encode.int32(payloadBytes.size, out)
// checksum
out.put(getChecksum(payloadBytes))
// message payload
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])
}
// message payload
return payloadBytes
}
companion object {

View File

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

View File

@ -35,6 +35,7 @@ import java.nio.ByteBuffer
import java.util.*
import java.util.Collections
import kotlin.collections.HashSet
import kotlin.collections.LinkedHashSet
private fun message(encoding: Plaintext.Encoding, subject: String, body: String): ByteArray = when (encoding) {
SIMPLE -> "Subject:$subject\nBody:$body".toByteArray()
@ -64,7 +65,7 @@ class Plaintext private constructor(
val message: ByteArray,
val ackData: ByteArray?,
ackMessage: Lazy<ObjectMessage?> = lazy { Factory.createAck(from, ackData, ttl) },
val conversationId: UUID = UUID.randomUUID(),
var conversationId: UUID = UUID.randomUUID(),
var inventoryVector: InventoryVector? = null,
var signature: ByteArray? = null,
sent: Long? = null,
@ -86,10 +87,10 @@ class Plaintext private constructor(
if (to == null) {
return
}
if (this.to != null) {
if (this.to!!.version != 0L)
this.to?.let {
if (it.version != 0L)
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")
}
}
@ -186,7 +187,8 @@ class Plaintext private constructor(
Factory.getObjectMessage(
3,
ByteArrayInputStream(ackMessage),
ackMessage.size)
ackMessage.size
)
} else null
},
conversationId = conversationId,
@ -242,7 +244,8 @@ class Plaintext private constructor(
Factory.getObjectMessage(
3,
ByteArrayInputStream(ackMsg),
ackMsg.size)
ackMsg.size
)
} else {
Factory.createAck(builder.from!!, builder.ackData, builder.ttl)
}
@ -254,108 +257,12 @@ class Plaintext private constructor(
received = builder.received,
initialHash = null,
ttl = builder.ttl,
labels = builder.labels,
labels = LinkedHashSet(builder.labels),
status = builder.status ?: Status.RECEIVED
) {
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() {
if (to != null) {
if (nextTry == null) {
@ -374,28 +281,30 @@ class Plaintext private constructor(
get() {
val s = Scanner(ByteArrayInputStream(message), "UTF-8")
val firstLine = s.nextLine()
if (encodingCode == EXTENDED.code) {
if (Message.TYPE == extendedData?.type) {
return (extendedData!!.content as? Message)?.subject
return when (encodingCode) {
EXTENDED.code -> if (Message.TYPE == extendedData?.type) {
(extendedData!!.content as? Message)?.subject
} else {
return null
null
}
SIMPLE.code -> firstLine.substring("Subject:".length).trim { it <= ' ' }
else -> {
if (firstLine.length > 50) {
firstLine.substring(0, 50).trim { it <= ' ' } + "..."
} else {
firstLine
}
}
} else if (encodingCode == SIMPLE.code) {
return firstLine.substring("Subject:".length).trim { it <= ' ' }
} else if (firstLine.length > 50) {
return firstLine.substring(0, 50).trim { it <= ' ' } + "..."
} else {
return firstLine
}
}
val text: String?
get() {
if (encodingCode == EXTENDED.code) {
if (Message.TYPE == extendedData?.type) {
return (extendedData?.content as Message?)?.body
return if (Message.TYPE == extendedData?.type) {
(extendedData?.content as Message?)?.body
} else {
return null
null
}
} else {
val text = String(message)
@ -418,20 +327,20 @@ class Plaintext private constructor(
val parents: List<InventoryVector>
get() {
val extendedData = extendedData ?: return emptyList()
if (Message.TYPE == extendedData.type) {
return (extendedData.content as Message).parents
return if (Message.TYPE == extendedData.type) {
(extendedData.content as Message).parents
} else {
return emptyList()
emptyList()
}
}
val files: List<Attachment>
get() {
val extendedData = extendedData ?: return emptyList()
if (Message.TYPE == extendedData.type) {
return (extendedData.content as Message).files
return if (Message.TYPE == extendedData.type) {
(extendedData.content as Message).files
} else {
return emptyList()
emptyList()
}
}
@ -474,8 +383,8 @@ class Plaintext private constructor(
override fun toString(): String {
val subject = subject
if (subject?.isNotEmpty() ?: false) {
return subject!!
if (subject?.isNotEmpty() == true) {
return subject
} else {
return Strings.hex(
initialHash ?: return super.toString()
@ -484,6 +393,7 @@ class Plaintext private constructor(
}
enum class Encoding constructor(code: Long) {
IGNORE(0), TRIVIAL(1), SIMPLE(2), EXTENDED(3);
var code: Long = 0
@ -495,7 +405,8 @@ class Plaintext private constructor(
companion object {
@JvmStatic fun fromCode(code: Long): Encoding? {
@JvmStatic
fun fromCode(code: Long): Encoding? {
for (e in values()) {
if (e.code == code) {
return e
@ -503,12 +414,13 @@ class Plaintext private constructor(
}
return null
}
}
}
enum class Status {
DRAFT,
// For sent messages
PUBKEY_REQUESTED,
DOING_PROOF_OF_WORK,
SENT,
@ -517,36 +429,158 @@ class Plaintext private constructor(
}
enum class Type {
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) {
internal var id: Any? = null
internal var inventoryVector: InventoryVector? = null
internal var from: BitmessageAddress? = null
internal var to: BitmessageAddress? = null
private var addressVersion: Long = 0
private var stream: Long = 0
private var behaviorBitfield: Int = 0
private var publicSigningKey: ByteArray? = null
private var publicEncryptionKey: ByteArray? = null
private var nonceTrialsPerByte: Long = 0
private var extraBytes: Long = 0
private var destinationRipe: ByteArray? = null
private var preventAck: Boolean = false
internal var encoding: Long = 0
internal var message = ByteArray(0)
internal var ackData: ByteArray? = null
internal var ackMessage: ByteArray? = null
internal var signature: ByteArray? = null
internal var sent: Long? = null
internal var received: Long? = null
internal var status: Status? = null
internal val labels = LinkedHashSet<Label>()
internal var ttl: Long = 0
internal var retries: Int = 0
internal var nextTry: Long? = null
internal var conversation: UUID? = null
var id: Any? = null
var inventoryVector: InventoryVector? = null
var from: BitmessageAddress? = null
var to: BitmessageAddress? = null
set(value) {
if (value != null) {
if (type != MSG && to != null)
throw IllegalArgumentException("recipient address only allowed for msg")
field = value
}
}
var addressVersion: Long = 0
var stream: Long = 0
var behaviorBitfield: Int = 0
var publicSigningKey: ByteArray? = null
var publicEncryptionKey: ByteArray? = null
var nonceTrialsPerByte: Long = 0
var extraBytes: Long = 0
var destinationRipe: ByteArray? = null
set(value) {
if (type != MSG && value != null) throw IllegalArgumentException("ripe only allowed for msg")
field = value
}
var preventAck: Boolean = false
var encoding: Long = 0
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 {
this.id = id
@ -564,11 +598,7 @@ class Plaintext private constructor(
}
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
}
@ -608,7 +638,6 @@ class Plaintext private constructor(
}
fun destinationRipe(ripe: ByteArray?): Builder {
if (type != MSG && ripe != null) throw IllegalArgumentException("ripe only allowed for msg")
this.destinationRipe = ripe
return this
}
@ -684,7 +713,7 @@ class Plaintext private constructor(
}
fun labels(labels: Collection<Label>): Builder {
this.labels.addAll(labels)
this.labels = labels
return this
}
@ -710,15 +739,17 @@ class Plaintext private constructor(
internal fun prepare(): Builder {
if (from == null) {
from = BitmessageAddress(Factory.createPubkey(
addressVersion,
stream,
publicSigningKey!!,
publicEncryptionKey!!,
nonceTrialsPerByte,
extraBytes,
behaviorBitfield
))
from = BitmessageAddress(
Factory.createPubkey(
addressVersion,
stream,
publicSigningKey!!,
publicEncryptionKey!!,
nonceTrialsPerByte,
extraBytes,
behaviorBitfield
)
)
}
if (to == null && type != Type.BROADCAST && destinationRipe != null) {
to = BitmessageAddress(0, 0, destinationRipe!!)
@ -726,7 +757,7 @@ class Plaintext private constructor(
if (preventAck) {
ackData = 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)
}
if (ttl <= 0) {
@ -735,6 +766,12 @@ class Plaintext private constructor(
return this
}
@JvmSynthetic
inline fun build(block: Builder.() -> Unit): Plaintext {
block(this)
return build()
}
fun build(): Plaintext {
return Plaintext(this)
}
@ -742,27 +779,54 @@ class Plaintext private constructor(
companion object {
@JvmStatic fun read(type: Type, `in`: InputStream): Plaintext {
return readWithoutSignature(type, `in`)
.signature(Decode.varBytes(`in`))
@JvmStatic
fun read(type: Type, input: InputStream): Plaintext {
return readWithoutSignature(type, input)
.signature(Decode.varBytes(input))
.received(UnixTime.now)
.build()
}
@JvmStatic fun readWithoutSignature(type: Type, `in`: InputStream): Plaintext.Builder {
val version = Decode.varInt(`in`)
@JvmStatic
fun readWithoutSignature(type: Type, input: InputStream): Plaintext.Builder {
val version = Decode.varInt(input)
return Builder(type)
.addressVersion(version)
.stream(Decode.varInt(`in`))
.behaviorBitfield(Decode.int32(`in`))
.publicSigningKey(Decode.bytes(`in`, 64))
.publicEncryptionKey(Decode.bytes(`in`, 64))
.nonceTrialsPerByte(if (version >= 3) Decode.varInt(`in`) else 0)
.extraBytes(if (version >= 3) Decode.varInt(`in`) else 0)
.destinationRipe(if (type == MSG) Decode.bytes(`in`, 20) else null)
.encoding(Decode.varInt(`in`))
.message(Decode.varBytes(`in`))
.ackMessage(if (type == MSG) Decode.varBytes(`in`) else null)
.stream(Decode.varInt(input))
.behaviorBitfield(Decode.int32(input))
.publicSigningKey(Decode.bytes(input, 64))
.publicEncryptionKey(Decode.bytes(input, 64))
.nonceTrialsPerByte(if (version >= 3) Decode.varInt(input) else 0)
.extraBytes(if (version >= 3) Decode.varInt(input) else 0)
.destinationRipe(if (type == MSG) Decode.bytes(input, 20).let {
if (it.any { x -> x != 0.toByte() }) it else null
} 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]
*/
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)
}
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 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) {
// 'verack' doesn't have any payload, so there is nothing to write
internal object EmptyWriter : StreamableWriter {
override fun write(out: OutputStream) = Unit
override fun write(buffer: ByteBuffer) = Unit
}
companion object {

View File

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

View File

@ -17,6 +17,7 @@
package ch.dissem.bitmessage.entity.payload
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.exception.DecryptionFailedException
import ch.dissem.bitmessage.utils.*
@ -108,44 +109,53 @@ class CryptoBox : Streamable {
private fun calculateMac(key_m: ByteArray): ByteArray {
val macData = ByteArrayOutputStream()
writeWithoutMAC(macData)
writer.writeWithoutMAC(macData)
return cryptography().mac(key_m, macData.toByteArray())
}
private fun writeWithoutMAC(out: OutputStream) {
out.write(initializationVector)
Encode.int16(curveType, out)
writeCoordinateComponent(out, Points.getX(R))
writeCoordinateComponent(out, Points.getY(R))
out.write(encrypted)
}
private val writer = Writer(this)
override fun writer(): StreamableWriter = writer
private fun writeCoordinateComponent(out: OutputStream, x: ByteArray) {
val offset = Bytes.numberOfLeadingZeros(x)
val length = x.size - offset
Encode.int16(length, out)
out.write(x, offset, length)
}
private class Writer(
private val item: CryptoBox
) : StreamableWriter {
private fun writeCoordinateComponent(buffer: ByteBuffer, x: ByteArray) {
val offset = Bytes.numberOfLeadingZeros(x)
val length = x.size - offset
Encode.int16(length, buffer)
buffer.put(x, offset, length)
}
override fun write(out: OutputStream) {
writeWithoutMAC(out)
out.write(item.mac)
}
override fun write(out: OutputStream) {
writeWithoutMAC(out)
out.write(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) {
val offset = Bytes.numberOfLeadingZeros(x)
val length = x.size - offset
Encode.int16(length, out)
out.write(x, offset, length)
}
private fun writeCoordinateComponent(buffer: ByteBuffer, x: ByteArray) {
val offset = Bytes.numberOfLeadingZeros(x)
val length = x.size - offset
Encode.int16(length, buffer)
buffer.put(x, offset, length)
}
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 {
@ -187,15 +197,14 @@ class CryptoBox : Streamable {
return this
}
fun build(): CryptoBox {
return CryptoBox(this)
}
fun build() = CryptoBox(this)
}
companion object {
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()
return Builder()
.IV(Decode.bytes(stream, 16, counter))

View File

@ -16,6 +16,7 @@
package ch.dissem.bitmessage.entity.payload
import ch.dissem.bitmessage.entity.SignedStreamableWriter
import ch.dissem.bitmessage.utils.Decode
import java.io.InputStream
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 fun write(out: OutputStream) {
out.write(data)
}
override fun write(buffer: ByteBuffer) {
buffer.put(data)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is GenericPayload) return false
@ -52,9 +45,27 @@ class GenericPayload(version: Long, override val stream: Long, val data: ByteArr
return result
}
companion object {
@JvmStatic fun read(version: Long, stream: Long, `is`: InputStream, length: Int): GenericPayload {
return GenericPayload(version, stream, Decode.bytes(`is`, length))
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 {
@JvmStatic
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
import ch.dissem.bitmessage.entity.BitmessageAddress
import ch.dissem.bitmessage.entity.SignedStreamableWriter
import ch.dissem.bitmessage.utils.Decode
import java.io.InputStream
import java.io.OutputStream
@ -47,17 +48,28 @@ class GetPubkey : ObjectPayload {
this.ripeTag = ripeOrTag
}
override fun write(out: OutputStream) {
out.write(ripeTag)
}
override fun writer(): SignedStreamableWriter = Writer(this)
private class Writer(
private val item: GetPubkey
) : SignedStreamableWriter {
override fun write(out: OutputStream) {
out.write(item.ripeTag)
}
override fun write(buffer: ByteBuffer) {
buffer.put(item.ripeTag)
}
override fun writeBytesToSign(out: OutputStream) = Unit // nothing to sign
override fun write(buffer: ByteBuffer) {
buffer.put(ripeTag)
}
companion object {
@JvmStatic fun read(`is`: InputStream, stream: Long, length: Int, version: Long): GetPubkey {
return GetPubkey(version, stream, Decode.bytes(`is`, length))
@JvmStatic
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.Type.MSG
import ch.dissem.bitmessage.entity.PlaintextHolder
import ch.dissem.bitmessage.entity.SignedStreamableWriter
import ch.dissem.bitmessage.exception.DecryptionFailedException
import java.io.InputStream
import java.io.OutputStream
@ -51,10 +52,6 @@ class Msg : ObjectPayload, Encrypted, PlaintextHolder {
override val isSigned: Boolean = true
override fun writeBytesToSign(out: OutputStream) {
plaintext?.write(out, false) ?: throw IllegalStateException("no plaintext data available")
}
override var signature: ByteArray?
get() = plaintext?.signature
set(signature) {
@ -73,14 +70,6 @@ class Msg : ObjectPayload, Encrypted, PlaintextHolder {
override val isDecrypted: Boolean
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 {
if (this === other) return true
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)
}
override fun hashCode(): Int {
return stream.toInt()
override fun hashCode() = 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 {
val ACK_LENGTH = 32
@JvmStatic fun read(`in`: InputStream, stream: Long, length: Int): Msg {
return Msg(stream, CryptoBox.read(`in`, length))
}
@JvmStatic
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
import ch.dissem.bitmessage.entity.ObjectMessage
import ch.dissem.bitmessage.entity.SignedStreamable
import ch.dissem.bitmessage.entity.Streamable
import java.io.OutputStream
/**
* 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?
@ -31,10 +32,6 @@ abstract class ObjectPayload protected constructor(val version: Long) : Streamab
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,
* * 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_NONCE_TRIALS_PER_BYTE
import ch.dissem.bitmessage.entity.EncryptedStreamableWriter
import ch.dissem.bitmessage.entity.SignedStreamableWriter
import ch.dissem.bitmessage.utils.Singleton.cryptography
import java.io.OutputStream
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 fun writeUnencrypted(out: OutputStream) {
write(out)
}
open fun writeUnencrypted(buffer: ByteBuffer) {
write(buffer)
}
abstract override fun writer(): EncryptedStreamableWriter
/**
* Bits 0 through 29 are yet undefined

View File

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

View File

@ -16,6 +16,7 @@
package ch.dissem.bitmessage.entity.payload
import ch.dissem.bitmessage.entity.EncryptedStreamableWriter
import ch.dissem.bitmessage.utils.Decode
import ch.dissem.bitmessage.utils.Encode
import java.io.InputStream
@ -34,32 +35,8 @@ class V3Pubkey protected constructor(
override var signature: ByteArray? = null
) : 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 fun writeBytesToSign(out: OutputStream) {
super.write(out)
Encode.varInt(nonceTrialsPerByte, out)
Encode.varInt(extraBytes, out)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is V3Pubkey) return false
@ -75,6 +52,37 @@ class V3Pubkey protected constructor(
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 {
private var streamNumber: Long = 0
private var behaviorBitfield: Int = 0
@ -134,16 +142,16 @@ class V3Pubkey protected constructor(
}
companion object {
@JvmStatic fun read(`is`: InputStream, stream: Long): V3Pubkey {
@JvmStatic fun read(input: InputStream, stream: Long): V3Pubkey {
return V3Pubkey(
version = 3,
stream = stream,
behaviorBitfield = Decode.int32(`is`),
signingKey = Decode.bytes(`is`, 64),
encryptionKey = Decode.bytes(`is`, 64),
nonceTrialsPerByte = Decode.varInt(`is`),
extraBytes = Decode.varInt(`is`),
signature = Decode.varBytes(`is`)
behaviorBitfield = Decode.int32(input),
signingKey = Decode.bytes(input, 64),
encryptionKey = Decode.bytes(input, 64),
nonceTrialsPerByte = Decode.varInt(input),
extraBytes = Decode.varInt(input),
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.Plaintext
import ch.dissem.bitmessage.entity.SignedStreamableWriter
import java.io.InputStream
import java.io.OutputStream
import java.nio.ByteBuffer
@ -38,22 +38,28 @@ open class V4Broadcast : Broadcast {
throw IllegalArgumentException("Address version 3 or older expected, but was " + senderAddress.version)
}
override fun writer(): SignedStreamableWriter = Writer(this)
override fun writeBytesToSign(out: OutputStream) {
plaintext?.write(out, false) ?: throw IllegalStateException("no plaintext data available")
}
protected open class Writer(
private val item: V4Broadcast
) : SignedStreamableWriter {
override fun write(out: OutputStream) {
encrypted?.write(out) ?: throw IllegalStateException("broadcast not encrypted")
}
override fun writeBytesToSign(out: OutputStream) {
item.plaintext?.writer(false)?.write(out) ?: throw IllegalStateException("no plaintext data available")
}
override fun write(buffer: ByteBuffer) {
encrypted?.write(buffer) ?: throw IllegalStateException("broadcast not encrypted")
override fun write(out: OutputStream) {
item.encrypted?.writer()?.write(out) ?: throw IllegalStateException("broadcast not encrypted")
}
override fun write(buffer: ByteBuffer) {
item.encrypted?.writer()?.write(buffer) ?: throw IllegalStateException("broadcast not encrypted")
}
}
companion object {
@JvmStatic fun read(`in`: InputStream, stream: Long, length: Int): V4Broadcast {
return V4Broadcast(4, stream, CryptoBox.read(`in`, length), null)
}
@JvmStatic
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.Encrypted
import ch.dissem.bitmessage.entity.EncryptedStreamableWriter
import ch.dissem.bitmessage.exception.DecryptionFailedException
import ch.dissem.bitmessage.utils.Decode
import java.io.InputStream
@ -63,29 +64,6 @@ class V4Pubkey : Pubkey, Encrypted {
override val isDecrypted: Boolean
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
get() = decrypted?.signingKey ?: throw IllegalStateException("pubkey is encrypted")
@ -126,14 +104,45 @@ class V4Pubkey : Pubkey, Encrypted {
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 {
@JvmStatic fun read(`in`: InputStream, stream: Long, length: Int, encrypted: Boolean): V4Pubkey {
if (encrypted)
return V4Pubkey(stream,
Decode.bytes(`in`, 32),
CryptoBox.read(`in`, length - 32))
else
return V4Pubkey(V3Pubkey.read(`in`, stream))
@JvmStatic
fun read(input: InputStream, stream: Long, length: Int, encrypted: Boolean) = if (encrypted) {
V4Pubkey(stream,
Decode.bytes(input, 32),
CryptoBox.read(input, length - 32))
} else {
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.Plaintext
import ch.dissem.bitmessage.entity.SignedStreamableWriter
import ch.dissem.bitmessage.utils.Decode
import java.io.InputStream
@ -40,19 +41,27 @@ class V5Broadcast : V4Broadcast {
this.tag = senderAddress.tag ?: throw IllegalStateException("version 4 address without tag")
}
override fun writeBytesToSign(out: OutputStream) {
out.write(tag)
super.writeBytesToSign(out)
}
override fun writer(): SignedStreamableWriter = Writer(this)
private class Writer(
private val item: V5Broadcast
) : V4Broadcast.Writer(item) {
override fun writeBytesToSign(out: OutputStream) {
out.write(item.tag)
super.writeBytesToSign(out)
}
override fun write(out: OutputStream) {
out.write(item.tag)
super.write(out)
}
override fun write(out: OutputStream) {
out.write(tag)
super.write(out)
}
companion object {
@JvmStatic fun read(`is`: InputStream, stream: Long, length: Int): V5Broadcast {
return V5Broadcast(stream, Decode.bytes(`is`, 32), CryptoBox.read(`is`, length - 32))
}
@JvmStatic
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
import ch.dissem.bitmessage.entity.Streamable
import ch.dissem.bitmessage.entity.StreamableWriter
import ch.dissem.bitmessage.utils.Strings
import java.io.OutputStream
import java.nio.ByteBuffer
@ -39,20 +40,29 @@ data class InventoryVector constructor(
return Arrays.hashCode(hash)
}
override fun write(out: OutputStream) {
out.write(hash)
}
override fun write(buffer: ByteBuffer) {
buffer.put(hash)
}
override fun toString(): String {
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 {
@JvmStatic fun fromHash(hash: ByteArray?): InventoryVector? {
@JvmStatic
fun fromHash(hash: ByteArray?): InventoryVector? {
return InventoryVector(
hash ?: return null
)

View File

@ -17,6 +17,7 @@
package ch.dissem.bitmessage.entity.valueobject
import ch.dissem.bitmessage.entity.Streamable
import ch.dissem.bitmessage.entity.StreamableWriter
import ch.dissem.bitmessage.entity.Version
import ch.dissem.bitmessage.utils.Encode
import ch.dissem.bitmessage.utils.UnixTime
@ -77,9 +78,7 @@ data class NetworkAddress(
fun provides(service: Version.Service?): Boolean = service?.isEnabled(services) ?: false
fun toInetAddress(): InetAddress {
return InetAddress.getByAddress(IPv6)
}
fun toInetAddress() = InetAddress.getByAddress(IPv6)
override fun equals(other: Any?): Boolean {
if (this === other) return true
@ -98,32 +97,40 @@ data class NetworkAddress(
return "[" + toInetAddress() + "]:" + port
}
override fun write(out: OutputStream) {
write(out, false)
}
fun writer(light: Boolean): StreamableWriter = Writer(
item = this,
light = light
)
fun write(out: OutputStream, light: Boolean) {
if (!light) {
Encode.int64(time, out)
Encode.int32(stream, out)
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) {
Encode.int64(item.time, out)
Encode.int32(item.stream, out)
}
Encode.int64(item.services, out)
out.write(item.IPv6)
Encode.int16(item.port, out)
}
Encode.int64(services, out)
out.write(IPv6)
Encode.int16(port, out)
}
override fun write(buffer: ByteBuffer) {
write(buffer, false)
}
fun write(buffer: ByteBuffer, light: Boolean) {
if (!light) {
Encode.int64(time, buffer)
Encode.int32(stream, buffer)
override fun write(buffer: ByteBuffer) {
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)
}
Encode.int64(services, buffer)
buffer.put(IPv6)
Encode.int16(port, buffer)
}
class Builder {
@ -194,6 +201,7 @@ data class NetworkAddress(
}
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.entity.BitmessageAddress
import ch.dissem.bitmessage.entity.Streamable
import ch.dissem.bitmessage.entity.StreamableWriter
import ch.dissem.bitmessage.entity.payload.Pubkey
import ch.dissem.bitmessage.exception.ApplicationException
import ch.dissem.bitmessage.factory.Factory
@ -66,7 +67,52 @@ data class PrivateKey(
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 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 {
@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 builder = Builder(version, stream, shorter).seed(passphrase)
for (i in 0..numberOfAddresses - 1) {
@ -176,13 +190,14 @@ data class PrivateKey(
return result
}
@JvmStatic fun read(`is`: InputStream): PrivateKey {
val version = Decode.varInt(`is`).toInt()
val stream = Decode.varInt(`is`)
val len = Decode.varInt(`is`).toInt()
val pubkey = Factory.readPubkey(version.toLong(), stream, `is`, len, false) ?: throw ApplicationException("Unknown pubkey version encountered")
val signingKey = Decode.varBytes(`is`)
val encryptionKey = Decode.varBytes(`is`)
@JvmStatic
fun read(input: InputStream): PrivateKey {
val version = Decode.varInt(input).toInt()
val stream = Decode.varInt(input)
val len = Decode.varInt(input).toInt()
val pubkey = Factory.readPubkey(version.toLong(), stream, input, len, false) ?: throw ApplicationException("Unknown pubkey version encountered")
val signingKey = Decode.varBytes(input)
val encryptionKey = Decode.varBytes(input)
return PrivateKey(signingKey, encryptionKey, pubkey)
}
}

View File

@ -37,33 +37,33 @@ import java.util.*
data class Message constructor(
val subject: String,
val body: String,
val parents: List<InventoryVector>,
val files: List<Attachment>
val parents: List<InventoryVector> = emptyList(),
val files: List<Attachment> = emptyList()
) : ExtendedEncoding.ExtendedType {
override val type: String = TYPE
override fun pack(): MPMap<MPString, MPType<*>> {
val result = MPMap<MPString, MPType<*>>()
result.put(mp(""), mp(TYPE))
result.put(mp("subject"), mp(subject))
result.put(mp("body"), mp(body))
result["".mp] = TYPE.mp
result["subject".mp] = subject.mp
result["body".mp] = body.mp
if (!files.isEmpty()) {
val items = MPArray<MPMap<MPString, MPType<*>>>()
result.put(mp("files"), items)
result["files".mp] = items
for (file in files) {
val item = MPMap<MPString, MPType<*>>()
item.put(mp("name"), mp(file.name))
item.put(mp("data"), mp(*file.data))
item.put(mp("type"), mp(file.type))
item.put(mp("disposition"), mp(file.disposition.name))
item["name".mp] = file.name.mp
item["data".mp] = file.data.mp
item["type".mp] = file.type.mp
item["disposition".mp] = file.disposition.name.mp
items.add(item)
}
}
if (!parents.isEmpty()) {
val items = MPArray<MPBinary>()
result.put(mp("parents"), items)
result["parents".mp] = items
for ((hash) in parents) {
items.add(mp(*hash))
}
@ -139,26 +139,26 @@ data class Message constructor(
override val type: String = TYPE
override fun unpack(map: MPMap<MPString, MPType<*>>): Message {
val subject = str(map[mp("subject")]) ?: ""
val body = str(map[mp("body")]) ?: ""
val subject = str(map["subject".mp]) ?: ""
val body = str(map["body".mp]) ?: ""
val parents = LinkedList<InventoryVector>()
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>>()) {
parents.add(InventoryVector.fromHash(
(parent as? MPBinary)?.value ?: continue
) ?: continue)
}
val mpFiles = map[mp("files")] as? MPArray<*>
val mpFiles = map["files".mp] as? MPArray<*>
for (item in mpFiles ?: emptyList<Any>()) {
if (item is MPMap<*, *>) {
val b = Attachment.Builder()
b.name(str(item[mp("name")])!!)
b.name(str(item["name".mp])!!)
b.data(
bin(item[mp("data")] ?: continue) ?: continue
bin(item["data".mp] ?: continue) ?: continue
)
b.type(str(item[mp("type")])!!)
val disposition = str(item[mp("disposition")])
b.type(str(item["type".mp])!!)
val disposition = str(item["disposition".mp])
if ("inline" == disposition) {
b.inline()
} else if ("attachment" == disposition) {
@ -179,6 +179,6 @@ data class Message constructor(
companion object {
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<*>> {
val result = MPMap<MPString, MPType<*>>()
result.put(mp(""), mp(TYPE))
result.put(mp("msgId"), mp(*msgId.hash))
result.put(mp("vote"), mp(vote))
result.put("".mp, TYPE.mp)
result.put("msgId".mp, msgId.hash.mp)
result.put("vote".mp, vote.mp)
return result
}
@ -77,13 +77,14 @@ data class Vote constructor(val msgId: InventoryVector, val vote: String) : Exte
get() = TYPE
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 vote = str(map[mp("vote")]) ?: throw IllegalArgumentException("no vote given")
val msgId = InventoryVector.fromHash((map["msgId".mp] as? MPBinary)?.value) ?: throw IllegalArgumentException("data doesn't contain proper msgId")
val vote = str(map["vote".mp]) ?: throw IllegalArgumentException("no vote given")
return Vote(msgId, vote)
}
}
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? {
try {
InflaterInputStream(ByteArrayInputStream(zippedData)).use { unzipper ->
val reader = Reader.getInstance()
@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]
if (messageType == null) {
LOG.error("Missing message type")

View File

@ -156,11 +156,11 @@ object Factory {
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()) {
2 -> return V2Pubkey.read(`is`, stream)
3 -> return V3Pubkey.read(`is`, stream)
4 -> return V4Pubkey.read(`is`, stream, length, encrypted)
2 -> return V2Pubkey.read(input, stream)
3 -> return V3Pubkey.read(input, stream)
4 -> return V4Pubkey.read(input, stream, length, encrypted)
}
LOG.debug("Unexpected pubkey version $version, handling as generic payload object")
return null

View File

@ -39,130 +39,88 @@ object V3MessageFactory {
private val LOG = LoggerFactory.getLogger(V3MessageFactory::class.java)
@JvmStatic
fun read(`in`: InputStream): NetworkMessage? {
findMagic(`in`)
val command = getCommand(`in`)
val length = Decode.uint32(`in`).toInt()
fun read(input: InputStream): NetworkMessage? {
findMagic(input)
val command = getCommand(input)
val length = Decode.uint32(input).toInt()
if (length > 1600003) {
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)) {
val payload = getPayload(command, ByteArrayInputStream(payloadBytes), length)
if (payload != null)
return NetworkMessage(payload)
else
return null
return payload?.let { NetworkMessage(payload) }
} else {
throw IOException("Checksum failed for message '$command'")
}
}
@JvmStatic
fun getPayload(command: String, stream: InputStream, length: Int): MessagePayload? {
when (command) {
"version" -> return parseVersion(stream)
"verack" -> return VerAck()
"addr" -> return parseAddr(stream)
"inv" -> return parseInv(stream)
"getdata" -> return parseGetData(stream)
"object" -> return readObject(stream, length)
"custom" -> return readCustom(stream, length)
else -> {
LOG.debug("Unknown command: " + command)
return null
}
fun getPayload(command: String, stream: InputStream, length: Int): MessagePayload? = when (command) {
"version" -> parseVersion(stream)
"verack" -> VerAck()
"addr" -> Addr(parseList(stream) { parseAddress(it, false) })
"inv" -> Inv(parseList(stream) { parseInventoryVector(it) })
"getdata" -> GetData(parseList(stream) { parseInventoryVector(it) })
"object" -> readObject(stream, length)
"custom" -> readCustom(stream, length)
else -> {
LOG.debug("Unknown command: $command")
null
}
}
private fun readCustom(`in`: InputStream, length: Int): MessagePayload {
return CustomMessage.read(`in`, length)
}
private fun readCustom(input: InputStream, length: Int): MessagePayload = CustomMessage.read(input, length)
@JvmStatic
fun readObject(`in`: InputStream, length: Int): ObjectMessage {
fun readObject(input: InputStream, length: Int): ObjectMessage {
val counter = AccessCounter()
val nonce = Decode.bytes(`in`, 8, counter)
val expiresTime = Decode.int64(`in`, counter)
val objectType = Decode.uint32(`in`, counter)
val version = Decode.varInt(`in`, counter)
val stream = Decode.varInt(`in`, counter)
val nonce = Decode.bytes(input, 8, counter)
val expiresTime = Decode.int64(input, counter)
val objectType = Decode.uint32(input, counter)
val version = Decode.varInt(input, counter)
val stream = Decode.varInt(input, counter)
val data = Decode.bytes(`in`, length - counter.length())
var payload: ObjectPayload
try {
val dataStream = ByteArrayInputStream(data)
payload = Factory.getObjectPayload(objectType, version, stream, dataStream, data.size)
val data = Decode.bytes(input, length - counter.length())
val payload: ObjectPayload = try {
Factory.getObjectPayload(objectType, version, stream, ByteArrayInputStream(data), data.size)
} catch (e: Exception) {
if (LOG.isTraceEnabled) {
LOG.trace("Could not parse object payload - using generic payload instead", e)
LOG.trace(Strings.hex(data))
}
payload = GenericPayload(version, stream, data)
GenericPayload(version, stream, data)
}
return ObjectMessage.Builder()
.nonce(nonce)
.expiresTime(expiresTime)
.objectType(objectType)
.stream(stream)
.payload(payload)
.build()
return ObjectMessage(
nonce, expiresTime, payload, objectType, version, stream
)
}
private fun parseGetData(stream: InputStream): GetData {
private fun <T> parseList(stream: InputStream, reader: (InputStream) -> (T)): List<T> {
val count = Decode.varInt(stream)
val inventoryVectors = LinkedList<InventoryVector>()
for (i in 0..count - 1) {
inventoryVectors.add(parseInventoryVector(stream))
val items = LinkedList<T>()
for (i in 0 until count) {
items.add(reader(stream))
}
return GetData(inventoryVectors)
return items
}
private fun parseInv(stream: InputStream): Inv {
val count = Decode.varInt(stream)
val inventoryVectors = LinkedList<InventoryVector>()
for (i in 0..count - 1) {
inventoryVectors.add(parseInventoryVector(stream))
}
return Inv(inventoryVectors)
}
private fun parseVersion(stream: InputStream) = Version(
version = Decode.int32(stream),
services = Decode.int64(stream),
timestamp = Decode.int64(stream),
addrRecv = parseAddress(stream, true),
addrFrom = parseAddress(stream, true),
nonce = Decode.int64(stream),
userAgent = Decode.varString(stream),
streams = Decode.varIntList(stream)
)
private fun parseAddr(stream: InputStream): Addr {
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 parseInventoryVector(stream: InputStream) = InventoryVector(Decode.bytes(stream, 32))
private fun parseAddress(stream: InputStream, light: Boolean): NetworkAddress {
val time: Long
@ -177,23 +135,15 @@ object V3MessageFactory {
val services = Decode.int64(stream)
val ipv6 = Decode.bytes(stream, 16)
val port = Decode.uint16(stream)
return NetworkAddress.Builder()
.time(time)
.stream(streamNumber)
.services(services)
.ipv6(ipv6)
.port(port)
.build()
return NetworkAddress(
time, streamNumber, services, ipv6, port
)
}
private fun testChecksum(checksum: ByteArray, payload: ByteArray): Boolean {
val payloadChecksum = cryptography().sha512(payload)
for (i in checksum.indices) {
if (checksum[i] != payloadChecksum[i]) {
return false
}
}
return true
return checksum.indices.none { checksum[it] != payloadChecksum[it] }
}
private fun getCommand(stream: InputStream): String {
@ -210,10 +160,10 @@ object V3MessageFactory {
return String(bytes, 0, end, Charsets.US_ASCII)
}
private fun findMagic(`in`: InputStream) {
private fun findMagic(input: InputStream) {
var pos = 0
for (i in 0..1619999) {
val b = `in`.read().toByte()
val b = input.read().toByte()
if (b == NetworkMessage.MAGIC_BYTES[pos]) {
if (pos + 1 == NetworkMessage.MAGIC_BYTES.size) {
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.
*/
class V3MessageReader {
private var headerBuffer: ByteBuffer? = null
private var dataBuffer: ByteBuffer? = null
val buffer: ByteBuffer = ByteBuffer.allocate(MAX_PAYLOAD_SIZE)
private var state: ReaderState? = ReaderState.MAGIC
private var command: String? = null
@ -40,89 +39,83 @@ class V3MessageReader {
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() {
if (state != ReaderState.DATA) {
getActiveBuffer() // in order to initialize
headerBuffer?.flip() ?: throw IllegalStateException("header buffer is null")
buffer.flip()
}
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"))
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
}
}
}
private fun magic(headerBuffer: ByteBuffer) {
if (!findMagicBytes(headerBuffer)) {
headerBuffer.compact()
return
} else {
state = ReaderState.HEADER
header(headerBuffer)
}
private fun magic(): ReaderState = if (!findMagicBytes(buffer)) {
buffer.compact()
ReaderState.WAIT_FOR_DATA
} else {
state = ReaderState.HEADER
ReaderState.HEADER
}
private fun header(headerBuffer: ByteBuffer) {
if (headerBuffer.remaining() < 20) {
headerBuffer.compact()
headerBuffer.limit(20)
return
private fun header(): ReaderState {
if (buffer.remaining() < 20) {
buffer.compact()
return ReaderState.WAIT_FOR_DATA
}
command = getCommand(headerBuffer)
length = Decode.uint32(headerBuffer).toInt()
command = getCommand(buffer)
length = Decode.uint32(buffer).toInt()
if (length > MAX_PAYLOAD_SIZE) {
throw NodeException("Payload of " + length + " bytes received, no more than " +
MAX_PAYLOAD_SIZE + " was expected.")
throw NodeException(
"Payload of " + length + " bytes received, no more than " +
MAX_PAYLOAD_SIZE + " was expected."
)
}
headerBuffer.get(checksum)
buffer.get(checksum)
state = ReaderState.DATA
this.headerBuffer = null
BufferPool.deallocate(headerBuffer)
val dataBuffer = BufferPool.allocate(length)
this.dataBuffer = dataBuffer
dataBuffer.clear()
dataBuffer.limit(length)
data(dataBuffer)
return ReaderState.DATA
}
private fun data(dataBuffer: ByteBuffer) {
if (dataBuffer.position() < length) {
return
} else {
dataBuffer.flip()
private fun data(flip: Boolean = true): ReaderState {
if (flip) {
if (buffer.position() < length) {
return ReaderState.WAIT_FOR_DATA
} else {
buffer.flip()
}
} else if (buffer.remaining() < length) {
buffer.compact()
return ReaderState.WAIT_FOR_DATA
}
if (!testChecksum(dataBuffer)) {
if (!testChecksum(buffer)) {
state = ReaderState.MAGIC
this.dataBuffer = null
BufferPool.deallocate(dataBuffer)
buffer.clear()
throw NodeException("Checksum failed for message '$command'")
}
try {
V3MessageFactory.getPayload(
command ?: throw IllegalStateException("command is null"),
ByteArrayInputStream(dataBuffer.array(),
dataBuffer.arrayOffset() + dataBuffer.position(), length),
ByteArrayInputStream(
buffer.array(),
buffer.arrayOffset() + buffer.position(), length
),
length
)?.let { messages.add(NetworkMessage(it)) }
} catch (e: IOException) {
throw NodeException(e.message)
} finally {
state = ReaderState.MAGIC
this.dataBuffer = null
BufferPool.deallocate(dataBuffer)
}
return ReaderState.MAGIC
}
fun getMessages(): MutableList<NetworkMessage> {
@ -163,8 +156,10 @@ class V3MessageReader {
}
private fun testChecksum(buffer: ByteBuffer): Boolean {
val payloadChecksum = cryptography().sha512(buffer.array(),
buffer.arrayOffset() + buffer.position(), length)
val payloadChecksum = cryptography().sha512(
buffer.array(),
buffer.arrayOffset() + buffer.position(), length
)
for (i in checksum.indices) {
if (checksum[i] != payloadChecksum[i]) {
return false
@ -173,17 +168,7 @@ class V3MessageReader {
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 {
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.
*/
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
@JvmField protected val ALGORITHM_ECDSA = "ECDSA"
@JvmField protected val ALGORITHM_ECDSA_SHA1 = "SHA1withECDSA"
@JvmField protected val ALGORITHM_EVP_SHA256 = "SHA256withECDSA"
@JvmField
protected val ALGORITHM_ECDSA = "ECDSA"
@JvmField
protected val ALGORITHM_ECDSA_SHA1 = "SHA1withECDSA"
@JvmField
protected val ALGORITHM_EVP_SHA256 = "SHA256withECDSA"
override fun setContext(context: InternalContext) {
ctx = context
@ -65,8 +69,12 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va
return mda.digest(mda.digest())
}
override fun doubleSha512(data: ByteArray, length: Int): ByteArray {
val mda = md("SHA-512")
override fun doubleSha512(data: ByteArray, length: Int) = doubleHash("SHA-512", data, length);
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)
return mda.digest(mda.digest())
}
@ -75,12 +83,6 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va
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 {
return hash("SHA-1", *data)
}
@ -91,13 +93,17 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va
return result
}
override fun doProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long,
extraBytes: Long, callback: ProofOfWorkEngine.Callback) {
override fun doProofOfWork(
objectMessage: ObjectMessage, nonceTrialsPerByte: Long,
extraBytes: Long, callback: ProofOfWorkEngine.Callback
) {
val initialHash = getInitialHash(objectMessage)
val target = getProofOfWorkTarget(objectMessage,
max(nonceTrialsPerByte, NETWORK_NONCE_TRIALS_PER_BYTE), max(extraBytes, NETWORK_EXTRA_BYTES))
val target = getProofOfWorkTarget(
objectMessage,
max(nonceTrialsPerByte, NETWORK_NONCE_TRIALS_PER_BYTE), max(extraBytes, NETWORK_EXTRA_BYTES)
)
ctx.proofOfWorkEngine.calculateNonce(initialHash, target, callback)
}
@ -105,7 +111,10 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va
@Throws(InsufficientProofOfWorkException::class)
override fun checkProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) {
val target = getProofOfWorkTarget(objectMessage, nonceTrialsPerByte, extraBytes)
val value = doubleSha512(objectMessage.nonce ?: throw ApplicationException("Object without nonce"), getInitialHash(objectMessage))
val value = doubleSha512(
objectMessage.nonce ?: throw ApplicationException("Object without nonce"),
getInitialHash(objectMessage)
)
if (Bytes.lt(target, value, 8)) {
throw InsufficientProofOfWorkException(target, value)
}
@ -136,7 +145,11 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va
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")
val nonceTrialsPerByte = if (nonceTrialsPerByte == 0L) NETWORK_NONCE_TRIALS_PER_BYTE else nonceTrialsPerByte
@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,
nonceTrialsPerByte: Long, extraBytes: Long, vararg features: Pubkey.Feature): Pubkey {
return Factory.createPubkey(version, stream,
override fun createPubkey(
version: Long, stream: Long, privateSigningKey: ByteArray, privateEncryptionKey: ByteArray,
nonceTrialsPerByte: Long, extraBytes: Long, vararg features: Pubkey.Feature
): Pubkey {
return Factory.createPubkey(
version, stream,
createPublicKey(privateSigningKey),
createPublicKey(privateEncryptionKey),
nonceTrialsPerByte, extraBytes, *features)
nonceTrialsPerByte, extraBytes, *features
)
}
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.valueobject.InventoryVector
import ch.dissem.bitmessage.entity.valueobject.Label
import ch.dissem.bitmessage.exception.ApplicationException
import ch.dissem.bitmessage.utils.SqlStrings.join
import ch.dissem.bitmessage.utils.Collections.single
import ch.dissem.bitmessage.utils.Strings
import ch.dissem.bitmessage.utils.UnixTime
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)}')")
}
override fun getConversation(conversationId: UUID): List<Plaintext> {
return find("conversation=X'${conversationId.toString().replace("-", "")}'")
}
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")
}
override fun getConversation(conversationId: UUID, offset: Int, limit: Int): List<Plaintext> {
return find("conversation=X'${conversationId.toString().replace("-", "")}'", offset, limit)
}
/**

View File

@ -156,6 +156,16 @@ interface Cryptography {
fun doProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long,
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
* *

View File

@ -35,9 +35,9 @@ open class DefaultLabeler : Labeler, InternalContext.ContextHolder {
msg.status = RECEIVED
val labelsToAdd =
if (msg.type == BROADCAST) {
ctx.messageRepository.getLabels(Label.Type.BROADCAST, Label.Type.UNREAD)
ctx.labelRepository.getLabels(Label.Type.BROADCAST, Label.Type.UNREAD)
} else {
ctx.messageRepository.getLabels(Label.Type.INBOX, Label.Type.UNREAD)
ctx.labelRepository.getLabels(Label.Type.INBOX, Label.Type.UNREAD)
}
msg.addLabels(labelsToAdd)
listener?.invoke(msg, labelsToAdd, emptyList())
@ -45,7 +45,7 @@ open class DefaultLabeler : Labeler, InternalContext.ContextHolder {
override fun markAsDraft(msg: Plaintext) {
msg.status = DRAFT
val labelsToAdd = ctx.messageRepository.getLabels(Label.Type.DRAFT)
val labelsToAdd = ctx.labelRepository.getLabels(Label.Type.DRAFT)
msg.addLabels(labelsToAdd)
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 }
msg.removeLabel(Label.Type.DRAFT)
val labelsToAdd = ctx.messageRepository.getLabels(Label.Type.OUTBOX)
val labelsToAdd = ctx.labelRepository.getLabels(Label.Type.OUTBOX)
msg.addLabels(labelsToAdd)
listener?.invoke(msg, labelsToAdd, labelsToRemove)
}
@ -67,7 +67,7 @@ open class DefaultLabeler : Labeler, InternalContext.ContextHolder {
msg.status = SENT
val labelsToRemove = msg.labels.filter { it.type == 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)
listener?.invoke(msg, labelsToAdd, labelsToRemove)
}
@ -83,7 +83,7 @@ open class DefaultLabeler : Labeler, InternalContext.ContextHolder {
}
override fun markAsUnread(msg: Plaintext) {
val labelsToAdd = ctx.messageRepository.getLabels(Label.Type.UNREAD)
val labelsToAdd = ctx.labelRepository.getLabels(Label.Type.UNREAD)
msg.addLabels(labelsToAdd)
listener?.invoke(msg, labelsToAdd, emptyList())
}
@ -91,7 +91,7 @@ open class DefaultLabeler : Labeler, InternalContext.ContextHolder {
override fun delete(msg: Plaintext) {
val labelsToRemove = msg.labels.toSet()
msg.labels.clear()
val labelsToAdd = ctx.messageRepository.getLabels(Label.Type.TRASH)
val labelsToAdd = ctx.labelRepository.getLabels(Label.Type.TRASH)
msg.addLabels(labelsToAdd)
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.*
interface MessageRepository {
fun getLabels(): List<Label>
fun getLabels(vararg types: Label.Type): List<Label>
fun save(label: Label)
fun countUnread(label: Label?): Int
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.
*/
fun findConversations(label: Label?): List<UUID>
fun findConversations(label: Label?, offset: Int = 0, limit: Int = 0): List<UUID>
fun findMessages(label: Label?): List<Plaintext>
@ -74,5 +68,5 @@ interface MessageRepository {
* *
* @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 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
fun loadStableNodes(): Map<Long, Set<NetworkAddress>> {
javaClass.classLoader.getResourceAsStream("nodes.txt").use { `in` ->
val scanner = Scanner(`in`)
javaClass.classLoader.getResourceAsStream("nodes.txt").use { input ->
val scanner = Scanner(input)
var stream: Long = 0
val result = HashMap<Long, Set<NetworkAddress>>()
var streamSet: MutableSet<NetworkAddress>? = null
@ -41,7 +41,7 @@ object NodeRegistryHelper {
val line = scanner.nextLine().trim { it <= ' ' }
if (line.startsWith("[stream")) {
stream = java.lang.Long.parseLong(line.substring(8, line.lastIndexOf(']')))
streamSet = HashSet<NetworkAddress>()
streamSet = HashSet()
result.put(stream, streamSet)
} else if (streamSet != null && !line.isEmpty() && !line.startsWith("#")) {
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
* 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 calculated nonce as argument. The ProofOfWorkEngine implementation must make
* * sure this is only called once.
* @param callback called with the initial hash and the calculated nonce as argument. The ProofOfWorkEngine
* implementation must make sure this is only called once.
*/
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 {
/**
* @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)
* into output bytes.
*/
private val ENCODE = charArrayOf(
private val ENCODE = charsAsBytes(
'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',
'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',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
).map { it.toByte() }.toByteArray()
)
/**
* Lookup table for turning Base64 alphabet positions (6 bits)
* 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',
'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',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'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
import ch.dissem.bitmessage.exception.ApplicationException
import java.util.*
object Collections {
@ -67,4 +68,12 @@ object Collections {
}
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
import ch.dissem.bitmessage.entity.Conversation
import ch.dissem.bitmessage.entity.Plaintext
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
import ch.dissem.bitmessage.entity.valueobject.Label
import ch.dissem.bitmessage.ports.MessageRepository
import org.slf4j.LoggerFactory
import java.util.*
import java.util.Collections
import java.util.regex.Pattern
@ -30,7 +31,11 @@ import java.util.regex.Pattern.CASE_INSENSITIVE
*/
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
@ -41,7 +46,7 @@ class ConversationService(private val messageRepository: MessageRepository) {
* *
* @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)
}
@ -58,8 +63,8 @@ class ConversationService(private val messageRepository: MessageRepository) {
return result
}
fun getConversation(conversationId: UUID): List<Plaintext> {
val messages = sorted(messageRepository.getConversation(conversationId))
fun getConversation(conversationId: UUID, limit: Int = 0): Conversation {
val messages = sorted(messageRepository.getConversation(conversationId, 0, limit))
val map = HashMap<InventoryVector, Plaintext>(messages.size)
for (message in messages) {
message.inventoryVector?.let {
@ -74,7 +79,7 @@ class ConversationService(private val messageRepository: MessageRepository) {
result.add(pos, last)
addAncestors(last, result, messages, map)
}
return result
return Conversation(conversationId, getSubject(result) ?: "", result)
}
fun getSubject(conversation: List<Plaintext>): String? {
@ -109,7 +114,12 @@ class ConversationService(private val messageRepository: MessageRepository) {
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) {
map.remove(parentKey)?.let {
messages.remove(it)

View File

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

View File

@ -25,21 +25,21 @@ import java.nio.ByteBuffer
* https://bitmessage.org/wiki/Protocol_specification#Common_structures
*/
object Decode {
@JvmStatic fun shortVarBytes(`in`: InputStream, counter: AccessCounter): ByteArray {
val length = uint16(`in`, counter)
return bytes(`in`, length, counter)
@JvmStatic fun shortVarBytes(input: InputStream, counter: AccessCounter): ByteArray {
val length = uint16(input, counter)
return bytes(input, length, counter)
}
@JvmStatic @JvmOverloads fun varBytes(`in`: InputStream, counter: AccessCounter? = null): ByteArray {
val length = varInt(`in`, counter).toInt()
return bytes(`in`, length, counter)
@JvmStatic @JvmOverloads fun varBytes(input: InputStream, counter: AccessCounter? = null): ByteArray {
val length = varInt(input, counter).toInt()
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)
var off = 0
while (off < count) {
val read = `in`.read(result, off, count - off)
val read = input.read(result, off, count - off)
if (read < 0) {
throw IOException("Unexpected end of stream, wanted to read $count bytes but only got $off")
}
@ -49,60 +49,58 @@ object Decode {
return result
}
@JvmStatic fun varIntList(`in`: InputStream): LongArray {
val length = varInt(`in`).toInt()
@JvmStatic fun varIntList(input: InputStream): LongArray {
val length = varInt(input).toInt()
val result = LongArray(length)
for (i in 0..length - 1) {
result[i] = varInt(`in`)
for (i in 0 until length) {
result[i] = varInt(input)
}
return result
}
@JvmStatic @JvmOverloads fun varInt(`in`: InputStream, counter: AccessCounter? = null): Long {
val first = `in`.read()
@JvmStatic @JvmOverloads fun varInt(input: InputStream, counter: AccessCounter? = null): Long {
val first = input.read()
AccessCounter.inc(counter)
when (first) {
0xfd -> return uint16(`in`, counter).toLong()
0xfe -> return uint32(`in`, counter)
0xff -> return int64(`in`, counter)
0xfd -> return uint16(input, counter).toLong()
0xfe -> return uint32(input, counter)
0xff -> return int64(input, counter)
else -> return first.toLong()
}
}
@JvmStatic fun uint8(`in`: InputStream): Int {
return `in`.read()
}
@JvmStatic fun uint8(input: InputStream): Int = input.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)
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)
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 {
return (u(`in`.get()) shl 24 or (u(`in`.get()) shl 16) or (u(`in`.get()) shl 8) or u(`in`.get())).toLong()
@JvmStatic fun uint32(input: ByteBuffer): Long {
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)
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)
return ByteBuffer.wrap(bytes(`in`, 8)).long
return ByteBuffer.wrap(bytes(input, 8)).long
}
@JvmStatic @JvmOverloads fun varString(`in`: InputStream, counter: AccessCounter? = null): String {
val length = varInt(`in`, counter).toInt()
@JvmStatic @JvmOverloads fun varString(input: InputStream, counter: AccessCounter? = null): String {
val length = varInt(input, counter).toInt()
// 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...
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
*/
object Encode {
@JvmStatic fun varIntList(values: LongArray, stream: OutputStream) {
@JvmStatic
fun varIntList(values: LongArray, stream: OutputStream) {
varInt(values.size, stream)
for (value in values) {
varInt(value, stream)
}
}
@JvmStatic fun varIntList(values: LongArray, buffer: ByteBuffer) {
@JvmStatic
fun varIntList(values: LongArray, buffer: ByteBuffer) {
varInt(values.size, buffer)
for (value in values) {
varInt(value, buffer)
}
}
@JvmStatic fun varInt(value: Int, buffer: ByteBuffer) = varInt(value.toLong(), buffer)
@JvmStatic fun varInt(value: Long, buffer: ByteBuffer) {
if (value < 0) {
// This is due to the fact that Java doesn't really support unsigned values.
// 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
// recognized as negatives aren't realistic.
buffer.put(0xff.toByte())
buffer.putLong(value)
} else if (value < 0xfd) {
buffer.put(value.toByte())
} else if (value <= 0xffffL) {
buffer.put(0xfd.toByte())
buffer.putShort(value.toShort())
} else if (value <= 0xffffffffL) {
buffer.put(0xfe.toByte())
buffer.putInt(value.toInt())
} else {
buffer.put(0xff.toByte())
buffer.putLong(value)
@JvmStatic
fun varInt(value: Number, buffer: ByteBuffer) {
val longValue = value.toLong()
when {
longValue < 0 -> {
// 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.
// Normally, negative values shouldn't occur within the protocol, and longs large enough for being
// recognized as negatives aren't realistic.
buffer.put(0xff.toByte())
buffer.putLong(longValue)
}
longValue < 0xfd -> {
buffer.put(value.toByte())
}
longValue <= 0xffffL -> {
buffer.put(0xfd.toByte())
buffer.putShort(value.toShort())
}
longValue <= 0xffffffffL -> {
buffer.put(0xfe.toByte())
buffer.putInt(value.toInt())
}
else -> {
buffer.put(0xff.toByte())
buffer.putLong(longValue)
}
}
}
@JvmStatic fun varInt(value: Int) = varInt(value.toLong())
@JvmStatic fun varInt(value: Long): ByteArray {
@JvmStatic
fun varInt(value: Number): ByteArray {
val buffer = ByteBuffer.allocate(9)
varInt(value, buffer)
buffer.flip()
return Bytes.truncate(buffer.array(), buffer.limit())
}
@JvmStatic @JvmOverloads fun varInt(value: Int, stream: OutputStream, counter: AccessCounter? = null) = varInt(value.toLong(), stream, counter)
@JvmStatic @JvmOverloads fun varInt(value: Long, stream: OutputStream, counter: AccessCounter? = null) {
@JvmStatic
@JvmOverloads
fun varInt(value: Number, stream: OutputStream, counter: AccessCounter? = null) {
val buffer = ByteBuffer.allocate(9)
varInt(value, buffer)
buffer.flip()
@ -80,46 +90,51 @@ object Encode {
AccessCounter.inc(counter, buffer.limit())
}
@JvmStatic @JvmOverloads fun int8(value: Long, stream: OutputStream, counter: AccessCounter? = null) = int8(value.toInt(), stream, counter)
@JvmStatic @JvmOverloads fun int8(value: Int, stream: OutputStream, counter: AccessCounter? = null) {
stream.write(value)
@JvmStatic
@JvmOverloads
fun int8(value: Number, stream: OutputStream, counter: AccessCounter? = null) {
stream.write(value.toInt())
AccessCounter.inc(counter)
}
@JvmStatic @JvmOverloads fun int16(value: Long, stream: OutputStream, counter: AccessCounter? = null) = int16(value.toShort(), stream, counter)
@JvmStatic @JvmOverloads fun int16(value: Int, stream: OutputStream, counter: AccessCounter? = null) = int16(value.toShort(), stream, counter)
@JvmStatic @JvmOverloads fun int16(value: Short, stream: OutputStream, counter: AccessCounter? = null) {
stream.write(ByteBuffer.allocate(2).putShort(value).array())
@JvmStatic
@JvmOverloads
fun int16(value: Number, stream: OutputStream, counter: AccessCounter? = null) {
stream.write(ByteBuffer.allocate(2).putShort(value.toShort()).array())
AccessCounter.inc(counter, 2)
}
@JvmStatic fun int16(value: Long, buffer: ByteBuffer) = int16(value.toShort(), buffer)
@JvmStatic fun int16(value: Int, buffer: ByteBuffer) = int16(value.toShort(), buffer)
@JvmStatic fun int16(value: Short, buffer: ByteBuffer) {
buffer.putShort(value)
@JvmStatic
fun int16(value: Number, buffer: ByteBuffer) {
buffer.putShort(value.toShort())
}
@JvmStatic @JvmOverloads fun int32(value: Long, stream: OutputStream, counter: AccessCounter? = null) = int32(value.toInt(), stream, counter)
@JvmStatic @JvmOverloads fun int32(value: Int, stream: OutputStream, counter: AccessCounter? = null) {
stream.write(ByteBuffer.allocate(4).putInt(value).array())
@JvmStatic
@JvmOverloads
fun int32(value: Number, stream: OutputStream, counter: AccessCounter? = null) {
stream.write(ByteBuffer.allocate(4).putInt(value.toInt()).array())
AccessCounter.inc(counter, 4)
}
@JvmStatic fun int32(value: Long, buffer: ByteBuffer) = int32(value.toInt(), buffer)
@JvmStatic fun int32(value: Int, buffer: ByteBuffer) {
buffer.putInt(value)
@JvmStatic
fun int32(value: Number, buffer: ByteBuffer) {
buffer.putInt(value.toInt())
}
@JvmStatic @JvmOverloads fun int64(value: Long, stream: OutputStream, counter: AccessCounter? = null) {
stream.write(ByteBuffer.allocate(8).putLong(value).array())
@JvmStatic
@JvmOverloads
fun int64(value: Number, stream: OutputStream, counter: AccessCounter? = null) {
stream.write(ByteBuffer.allocate(8).putLong(value.toLong()).array())
AccessCounter.inc(counter, 8)
}
@JvmStatic fun int64(value: Long, buffer: ByteBuffer) {
buffer.putLong(value)
@JvmStatic
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"))
// 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.
@ -128,7 +143,8 @@ object Encode {
out.write(bytes)
}
@JvmStatic fun varString(value: String, buffer: ByteBuffer) {
@JvmStatic
fun varString(value: String, buffer: ByteBuffer) {
val bytes = value.toByteArray()
// 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.
@ -137,12 +153,14 @@ object Encode {
buffer.put(bytes)
}
@JvmStatic fun varBytes(data: ByteArray, out: OutputStream) {
@JvmStatic
fun varBytes(data: ByteArray, out: OutputStream) {
varInt(data.size.toLong(), out)
out.write(data)
}
@JvmStatic fun varBytes(data: ByteArray, buffer: ByteBuffer) {
@JvmStatic
fun varBytes(data: ByteArray, buffer: ByteBuffer) {
varInt(data.size.toLong(), buffer)
buffer.put(data)
}
@ -152,9 +170,10 @@ object Encode {
* @param streamable the object to be serialized
* @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()
streamable.write(stream)
streamable.writer().write(stream)
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*
* @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()
streamable.write(stream)
streamable.writer().write(stream)
val offset = padding - stream.size() % padding
val length = stream.size() + offset
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.UnixTime.MINUTE
import com.nhaarman.mockito_kotlin.*
import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.CoreMatchers.notNullValue
import org.junit.Assert.*
import org.junit.Before
import org.junit.Test
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import java.util.*
import kotlin.concurrent.thread
@ -47,15 +45,16 @@ import kotlin.concurrent.thread
* @author Christian Basler
*/
class BitmessageContextTest {
private var listener: BitmessageContext.Listener = mock()
private val inventory = spy(TestInventory())
private var testListener: BitmessageContext.Listener = mock()
private val testInventory = spy(TestInventory())
private val testPowRepo = spy(object : ProofOfWorkRepository {
internal var items: MutableMap<InventoryVector, ProofOfWorkRepository.Item> = HashMap()
internal var added = 0
internal var removed = 0
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> {
@ -72,7 +71,10 @@ class BitmessageContextTest {
}
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++
}
@ -93,26 +95,27 @@ class BitmessageContextTest {
thread { callback.onNonceCalculated(initialHash, ByteArray(8)) }
}
})
private var ctx = BitmessageContext.Builder()
.addressRepo(mock())
.cryptography(BouncyCryptography())
.inventory(inventory)
.listener(listener)
.messageRepo(mock())
.networkHandler(mock {
private var ctx = BitmessageContext.build {
addressRepo = mock()
cryptography = BouncyCryptography()
inventory = testInventory
listener = testListener
labelRepo = mock()
messageRepo = mock()
networkHandler = mock {
on { getNetworkStatus() } doReturn Property("test", "mocked")
})
.nodeRegistry(mock())
.labeler(spy(DefaultLabeler()))
.powRepo(testPowRepo)
.proofOfWorkEngine(testPowEngine)
.build()
}
nodeRegistry = mock()
labeler = spy(DefaultLabeler())
proofOfWorkRepo = testPowRepo
proofOfWorkEngine = testPowEngine
}
init {
TTL.msg = 2 * MINUTE
}
@Before
@BeforeEach
fun setUp() {
testPowRepo.reset()
}
@ -125,7 +128,7 @@ class BitmessageContextTest {
ctx.addContact(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
@ -138,12 +141,12 @@ class BitmessageContextTest {
ctx.addContact(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
fun `ensure V2Pubkey is not requested if it exists in inventory`() {
inventory.init(
testInventory.init(
"V1Msg.payload",
"V2GetPubkey.payload",
"V2Pubkey.payload",
@ -161,12 +164,12 @@ class BitmessageContextTest {
ctx.addContact(contact)
verify(ctx.addresses, atLeastOnce()).save(contact)
verify(testPowEngine, never()).calculateNonce(any(), any(), any())
verify(testPowEngine, never()).calculateNonce(any(), any(), any<ProofOfWorkEngine.Callback>())
}
@Test
fun `ensure V4Pubkey is not requested if it exists in inventory`() {
inventory.init(
testInventory.init(
"V1Msg.payload",
"V2GetPubkey.payload",
"V2Pubkey.payload",
@ -185,14 +188,14 @@ class BitmessageContextTest {
ctx.addContact(contact)
verify(ctx.addresses, atLeastOnce()).save(any())
verify(testPowEngine, never()).calculateNonce(any(), any(), any())
verify(testPowEngine, never()).calculateNonce(any(), any(), any<ProofOfWorkEngine.Callback>())
}
@Test
fun `ensure subscription is added and existing broadcasts retrieved`() {
val address = BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ")
inventory.init(
testInventory.init(
"V4Broadcast.payload",
"V5Broadcast.payload"
)
@ -201,60 +204,73 @@ class BitmessageContextTest {
ctx.addSubscribtion(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(listener).receive(any())
verify(testListener).receive(any())
}
@Test
fun `ensure identity is created`() {
assertThat(ctx.createIdentity(false), notNullValue())
assertNotNull(ctx.createIdentity(false))
}
@Test
fun `ensure message is sent`() {
ctx.send(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), TestUtils.loadContact(),
"Subject", "Message")
ctx.send(
TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), TestUtils.loadContact(),
"Subject", "Message"
)
verify(ctx.internals.proofOfWorkRepository, timeout(10000)).putObject(
argThat { payload.type == ObjectType.MSG }, eq(1000L), eq(1000L))
argThat { payload.type == ObjectType.MSG }, eq(1000L), eq(1000L)
)
assertEquals(2, testPowRepo.added)
verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat<Plaintext> { type == Type.MSG })
}
@Test
fun `ensure pubkey is requested if it is missing`() {
ctx.send(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"),
ctx.send(
TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"),
BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"),
"Subject", "Message")
"Subject", "Message"
)
verify(testPowRepo, timeout(10000).atLeastOnce())
.putObject(argThat { payload.type == ObjectType.GET_PUBKEY }, eq(1000L), eq(1000L))
verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat<Plaintext> { type == Type.MSG })
}
@Test(expected = IllegalArgumentException::class)
@Test
fun `ensure sender must be identity`() {
ctx.send(BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"),
BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"),
"Subject", "Message")
assertThrows(IllegalArgumentException::class.java) {
ctx.send(
BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"),
BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"),
"Subject", "Message"
)
}
}
@Test
fun `ensure broadcast is sent`() {
ctx.broadcast(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"),
"Subject", "Message")
ctx.broadcast(
TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"),
"Subject", "Message"
)
verify(ctx.internals.proofOfWorkRepository, timeout(1000).atLeastOnce())
.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 })
}
@Test(expected = IllegalArgumentException::class)
@Test
fun `ensure sender without private key throws exception`() {
val msg = Plaintext.Builder(Type.BROADCAST)
.from(BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
.message("Subject", "Message")
.build()
ctx.send(msg)
assertThrows(IllegalArgumentException::class.java) {
val msg = Plaintext.Builder(Type.BROADCAST)
.from(BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
.message("Subject", "Message")
.build()
ctx.send(msg)
}
}
@Test
@ -325,6 +341,6 @@ class BitmessageContextTest {
@Test
fun `ensure status contains user agent`() {
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.utils.TestBase
import ch.dissem.bitmessage.utils.TestUtils
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
class DecryptionTest : TestBase() {
@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.now
import com.nhaarman.mockito_kotlin.*
import org.junit.Before
import org.junit.Test
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
/**
* @author Christian Basler
@ -47,7 +47,7 @@ class DefaultMessageListenerTest : TestBase() {
cryptography = BouncyCryptography()
)
@Before
@BeforeAll
fun setUp() {
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.TestBase
import ch.dissem.bitmessage.utils.TestUtils
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Test
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Test
class EncryptionTest : TestBase() {
@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.Msg
import ch.dissem.bitmessage.ports.Cryptography
import ch.dissem.bitmessage.ports.ProofOfWorkEngine
import ch.dissem.bitmessage.ports.ProofOfWorkRepository
import ch.dissem.bitmessage.utils.Singleton
import ch.dissem.bitmessage.utils.TestUtils
import com.nhaarman.mockito_kotlin.*
import org.hamcrest.CoreMatchers.equalTo
import org.junit.Assert.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import java.util.*
import kotlin.properties.Delegates
@ -43,7 +43,7 @@ class ProofOfWorkServiceTest {
private var obj by Delegates.notNull<ObjectMessage>()
@Before
@BeforeEach
fun setUp() {
cryptography = spy(BouncyCryptography())
Singleton.initialize(cryptography)
@ -65,11 +65,11 @@ class ProofOfWorkServiceTest {
fun `ensure missing proof of work is done`() {
whenever(ctx.proofOfWorkRepository.getItems()).thenReturn(Arrays.asList<ByteArray>(ByteArray(64)))
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)
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
@ -95,6 +95,6 @@ class ProofOfWorkServiceTest {
verify(ctx.proofOfWorkRepository).removeObject(eq(initialHash))
verify(ctx.inventory).storeObject(eq(objectMessage))
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.utils.TestBase
import ch.dissem.bitmessage.utils.TestUtils
import org.junit.Assert.*
import org.junit.Test
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
class SignatureTest : TestBase() {
@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.valueobject.PrivateKey
import ch.dissem.bitmessage.utils.*
import org.junit.Assert
import org.junit.Assert.*
import org.junit.Test
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
import java.io.IOException
import java.util.*
class BitmessageAddressTest : TestBase() {
@Test
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(3, Pubkey.Feature.bitfield(DOES_ACK, INCLUDE_DESTINATION))
}
@ -74,7 +73,7 @@ class BitmessageAddressTest : TestBase() {
try {
address.pubkey = pubkey
} catch (e: Exception) {
fail(e.message)
fail<Unit>(e.message)
}
}
@ -82,7 +81,7 @@ class BitmessageAddressTest : TestBase() {
@Test
fun `ensure V3Pubkey can be imported`() {
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 pubkey = objectMessage.payload as Pubkey
@ -90,7 +89,7 @@ class BitmessageAddressTest : TestBase() {
try {
address.pubkey = pubkey
} catch (e: Exception) {
fail(e.message)
fail<Unit>(e.message)
}
assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), pubkey.ripe)
@ -107,7 +106,7 @@ class BitmessageAddressTest : TestBase() {
try {
address.pubkey = pubkey
} catch (e: Exception) {
fail(e.message)
fail<Unit>(e.message)
}
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.utils.TestBase
import ch.dissem.bitmessage.utils.TestUtils
import org.hamcrest.Matchers.`is`
import org.junit.Assert.*
import org.junit.Test
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.ObjectInputStream
@ -86,16 +85,16 @@ class SerializationTest : TestBase() {
.signature(ByteArray(0))
.build()
val out = ByteArrayOutputStream()
expected.write(out)
val `in` = ByteArrayInputStream(out.toByteArray())
val actual = Plaintext.read(MSG, `in`)
expected.writer().write(out)
val input = ByteArrayInputStream(out.toByteArray())
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)
assertThat(expected, `is`(actual))
assertEquals(actual, expected)
}
@Test
@ -103,17 +102,46 @@ class SerializationTest : TestBase() {
val expected = Plaintext.Builder(MSG)
.from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
.to(TestUtils.loadContact())
.message(Message.Builder()
.subject("Subject")
.body("Message")
.build())
.message(
Message.Builder()
.subject("Subject")
.body("Message")
.build()
)
.ackData("ackMessage".toByteArray())
.signature(ByteArray(0))
.build()
val out = ByteArrayOutputStream()
expected.write(out)
val `in` = ByteArrayInputStream(out.toByteArray())
val actual = Plaintext.read(MSG, `in`)
expected.writer().write(out)
val input = ByteArrayInputStream(out.toByteArray())
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
val received = Plaintext::class.java.getDeclaredField("received")
@ -136,9 +164,9 @@ class SerializationTest : TestBase() {
assertNotNull(ackMessage1)
val out = ByteArrayOutputStream()
expected.write(out)
val `in` = ByteArrayInputStream(out.toByteArray())
val actual = Plaintext.read(MSG, `in`)
expected.writer().write(out)
val input = ByteArrayInputStream(out.toByteArray())
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")
@ -159,7 +187,7 @@ class SerializationTest : TestBase() {
val inv = Inv(ivs)
val before = NetworkMessage(inv)
val out = ByteArrayOutputStream()
before.write(out)
before.writer().write(out)
val after = Factory.getNetworkMessage(3, ByteArrayInputStream(out.toByteArray()))
assertNotNull(after)
@ -169,11 +197,11 @@ class SerializationTest : TestBase() {
private fun doTest(resourceName: String, version: Int, expectedPayloadType: Class<*>) {
val data = TestUtils.getBytes(resourceName)
val `in` = ByteArrayInputStream(data)
val objectMessage = Factory.getObjectMessage(version, `in`, data.size)
val input = ByteArrayInputStream(data)
val objectMessage = Factory.getObjectMessage(version, input, data.size)
val out = ByteArrayOutputStream()
assertNotNull(objectMessage)
objectMessage!!.write(out)
objectMessage!!.writer().write(out)
assertArrayEquals(data, out.toByteArray())
assertEquals(expectedPayloadType.canonicalName, objectMessage.payload.javaClass.canonicalName)
}
@ -190,8 +218,8 @@ class SerializationTest : TestBase() {
val oos = ObjectOutputStream(out)
oos.writeObject(plaintext)
val `in` = ByteArrayInputStream(out.toByteArray())
val ois = ObjectInputStream(`in`)
val input = ByteArrayInputStream(out.toByteArray())
val ois = ObjectInputStream(input)
assertEquals(plaintext, ois.readObject())
}
}

View File

@ -20,18 +20,24 @@ import ch.dissem.bitmessage.utils.Bytes
import ch.dissem.bitmessage.utils.CallbackWaiter
import ch.dissem.bitmessage.utils.Singleton.cryptography
import ch.dissem.bitmessage.utils.TestBase
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.jupiter.api.Assertions.assertTimeoutPreemptively
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import java.time.Duration.ofSeconds
class ProofOfWorkEngineTest : TestBase() {
@Test(timeout = 90000)
@Test
fun `test SimplePOWEngine`() {
testPOW(SimplePOWEngine())
assertTimeoutPreemptively(ofSeconds(90)) {
testPOW(SimplePOWEngine())
}
}
@Test(timeout = 90000)
@Test
fun `test MultiThreadedPOWEngine`() {
testPOW(MultiThreadedPOWEngine())
assertTimeoutPreemptively(ofSeconds(90)) {
testPOW(MultiThreadedPOWEngine())
}
}
private fun testPOW(engine: ProofOfWorkEngine) {
@ -65,6 +71,6 @@ class ProofOfWorkEngineTest : TestBase() {
val nonce2 = waiter2.waitForValue()!!
println("Calculating nonce1 took ${waiter2.time}ms")
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
import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography
import org.hamcrest.Matchers.`is`
import org.junit.Assert.assertThat
import org.junit.Test
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class Base64Test {
@Test
@ -29,7 +28,7 @@ class Base64Test {
val data = cryptography.randomBytes(i)
val string = Base64.encodeToString(data)
val decoded = Base64.decode(string)
assertThat(decoded, `is`(data))
assertEquals(data, decoded)
}
}
}

View File

@ -16,10 +16,10 @@
package ch.dissem.bitmessage.utils
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Ignore
import org.junit.Test
import org.junit.jupiter.api.Assertions.assertArrayEquals
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import java.math.BigInteger
import java.util.*
@ -46,7 +46,11 @@ class BytesTest {
for (i in 1..255) {
val bytes = byteArrayOf(0, v.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.
*/
@Test
@Ignore
@Disabled
fun `test lower than single byte`() {
val a = 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 b = BigInteger.valueOf(rnd.nextLong()).pow(rnd.nextInt(5) + 1).abs()
println("a = " + a.toString(16) + "\tb = " + b.toString(16))
assertEquals(a.compareTo(b) == -1, Bytes.lt(
Bytes.expand(a.toByteArray(), 100),
Bytes.expand(b.toByteArray(), 100),
100))
assertEquals(
a.compareTo(b) == -1, Bytes.lt(
Bytes.expand(a.toByteArray(), 100),
Bytes.expand(b.toByteArray(), 100),
100
)
)
}
}
}

View File

@ -16,17 +16,12 @@
package ch.dissem.bitmessage.utils
import org.junit.Test
import java.util.LinkedList
import org.junit.Assert.assertEquals
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class CollectionsTest {
@Test
fun `ensure select random returns maximum possible items`() {
val list = LinkedList<Int>()
list += 0..9
assertEquals(9, Collections.selectRandom(9, list).size)
assertEquals(9, Collections.selectRandom(9, listOf(0..9)).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.utils.TestUtils.RANDOM
import com.nhaarman.mockito_kotlin.*
import org.hamcrest.Matchers.`is`
import org.junit.Assert.assertThat
import org.junit.Test
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import java.util.*
class ConversationServiceTest : TestBase() {
@ -45,7 +44,7 @@ class ConversationServiceTest : TestBase() {
doReturn(expected).whenever(conversationService).getConversation(any<UUID>())
val actual = conversationService.getConversation(UUID.randomUUID())
assertThat(actual, `is`(expected))
Assertions.assertEquals(expected, actual)
}
companion object {
@ -53,57 +52,69 @@ class ConversationServiceTest : TestBase() {
fun conversation(alice: BitmessageAddress, bob: BitmessageAddress): List<Plaintext> {
val result = LinkedList<Plaintext>()
val older = plaintext(alice, bob,
val older = plaintext(
alice, bob,
Message.Builder()
.subject("hey there")
.body("does it work?")
.build(),
Plaintext.Status.SENT)
Plaintext.Status.SENT
)
result.add(older)
val root = plaintext(alice, bob,
val root = plaintext(
alice, bob,
Message.Builder()
.subject("new test")
.body("There's a new test in town!")
.build(),
Plaintext.Status.SENT)
Plaintext.Status.SENT
)
result.add(root)
result.add(
plaintext(bob, alice,
plaintext(
bob, alice,
Message.Builder()
.subject("Re: new test (1a)")
.body("Nice!")
.addParent(root)
.build(),
Plaintext.Status.RECEIVED)
Plaintext.Status.RECEIVED
)
)
val latest = plaintext(bob, alice,
val latest = plaintext(
bob, alice,
Message.Builder()
.subject("Re: new test (2b)")
.body("PS: it did work!")
.addParent(root)
.addParent(older)
.build(),
Plaintext.Status.RECEIVED)
Plaintext.Status.RECEIVED
)
result.add(latest)
result.add(
plaintext(alice, bob,
plaintext(
alice, bob,
Message.Builder()
.subject("Re: new test (2)")
.body("")
.addParent(latest)
.build(),
Plaintext.Status.DRAFT)
Plaintext.Status.DRAFT
)
)
return result
}
fun plaintext(from: BitmessageAddress, to: BitmessageAddress,
content: ExtendedEncoding, status: Plaintext.Status): Plaintext {
fun plaintext(
from: BitmessageAddress, to: BitmessageAddress,
content: ExtendedEncoding, status: Plaintext.Status
): Plaintext {
val builder = Plaintext.Builder(MSG)
.IV(TestUtils.randomInventoryVector())
.from(from)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,6 +18,7 @@ package ch.dissem.bitmessage.utils
import ch.dissem.bitmessage.BitmessageContext
import ch.dissem.bitmessage.InternalContext
import ch.dissem.bitmessage.Preferences
import ch.dissem.bitmessage.entity.BitmessageAddress
import ch.dissem.bitmessage.entity.ObjectMessage
import ch.dissem.bitmessage.entity.payload.V4Pubkey
@ -28,7 +29,7 @@ import ch.dissem.bitmessage.factory.Factory
import ch.dissem.bitmessage.ports.*
import com.nhaarman.mockito_kotlin.mock
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.ByteArrayOutputStream
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.
*/
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()
Encode.int16(number, out)
return out.toByteArray()
}
@JvmStatic fun loadObjectMessage(version: Int, resourceName: String): ObjectMessage {
@JvmStatic
fun loadObjectMessage(version: Int, resourceName: String): ObjectMessage {
val data = getBytes(resourceName)
val `in` = ByteArrayInputStream(data)
return Factory.getObjectMessage(version, `in`, data.size) ?: throw NoSuchElementException("error loading object message")
val input = ByteArrayInputStream(data)
return Factory.getObjectMessage(version, input, data.size)
?: throw NoSuchElementException("error loading object message")
}
@JvmStatic fun getBytes(resourceName: String): ByteArray {
val `in` = javaClass.classLoader.getResourceAsStream(resourceName)
@JvmStatic
fun getBytes(resourceName: String): ByteArray {
val input = javaClass.classLoader.getResourceAsStream(resourceName)
val out = ByteArrayOutputStream()
val buffer = ByteArray(1024)
var len = `in`.read(buffer)
var len = input.read(buffer)
while (len != -1) {
out.write(buffer, 0, len)
len = `in`.read(buffer)
len = input.read(buffer)
}
return out.toByteArray()
}
@JvmStatic fun randomInventoryVector(): InventoryVector {
@JvmStatic
fun randomInventoryVector(): InventoryVector {
val bytes = ByteArray(32)
RANDOM.nextBytes(bytes)
return InventoryVector(bytes)
}
@JvmStatic fun getResource(resourceName: String): InputStream {
return javaClass.classLoader.getResourceAsStream(resourceName)
}
@JvmStatic
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 identity = BitmessageAddress(privateKey)
assertEquals(address, identity.address)
@ -83,7 +91,8 @@ object TestUtils {
}
@Throws(DecryptionFailedException::class)
@JvmStatic fun loadContact(): BitmessageAddress {
@JvmStatic
fun loadContact(): BitmessageAddress {
val address = BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h")
val objectMessage = TestUtils.loadObjectMessage(3, "V4Pubkey.payload")
objectMessage.decrypt(address.publicDecryptionKey)
@ -91,18 +100,21 @@ object TestUtils {
return address
}
@JvmStatic fun loadPubkey(address: BitmessageAddress) {
@JvmStatic
fun loadPubkey(address: BitmessageAddress) {
val bytes = getBytes(address.address + ".pubkey")
val pubkey = Factory.readPubkey(address.version, address.stream, ByteArrayInputStream(bytes), bytes.size, false)
address.pubkey = pubkey
}
@JvmStatic fun mockedInternalContext(
@JvmStatic
fun mockedInternalContext(
cryptography: Cryptography = mock {},
inventory: Inventory = mock {},
nodeRegistry: NodeRegistry = mock {},
networkHandler: NetworkHandler = mock {},
addressRepository: AddressRepository = mock {},
labelRepository: LabelRepository = mock {},
messageRepository: MessageRepository = mock {},
proofOfWorkRepository: ProofOfWorkRepository = mock {},
proofOfWorkEngine: ProofOfWorkEngine = mock {},
@ -119,16 +131,19 @@ object TestUtils {
nodeRegistry,
networkHandler,
addressRepository,
labelRepository,
messageRepository,
proofOfWorkRepository,
proofOfWorkEngine,
customCommandHandler,
listener,
labeler,
"/Jabit:TEST/",
port,
connectionTTL,
connectionLimit
Preferences().apply {
this.userAgent = "/Jabit:TEST/"
this.port = port
this.connectionTTL = connectionTTL
this.connectionLimit = connectionLimit
}
))
}
}

View File

@ -13,7 +13,8 @@ uploadArchives {
dependencies {
compile project(':core')
compile 'org.bouncycastle:bcprov-jdk15on'
testCompile 'junit:junit'
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')
}

View File

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

View File

@ -13,7 +13,8 @@ uploadArchives {
dependencies {
compile project(':core')
compile 'com.madgag.spongycastle:prov'
testCompile 'junit:junit'
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')
}

View File

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

View File

@ -24,15 +24,16 @@ task fatCapsule(type: FatCapsule) {
}
dependencies {
compile project(':core')
compile project(':networking')
compile project(':repositories')
compile project(':cryptography-bc')
compile project(':wif')
compile 'org.slf4j:slf4j-simple'
compile 'args4j:args4j'
compile 'com.h2database:h2'
compile 'org.apache.commons:commons-lang3'
testCompile 'junit:junit'
testCompile 'com.nhaarman:mockito-kotlin'
implementation project(':core')
implementation project(':networking')
implementation project(':repositories')
implementation project(':cryptography-bc')
implementation project(':wif')
implementation 'org.slf4j:slf4j-simple'
implementation 'args4j:args4j'
implementation 'com.h2database:h2'
implementation 'org.apache.commons:commons-text'
testImplementation '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.valueobject.Label;
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.LoggerFactory;
@ -115,6 +115,7 @@ public class Application {
System.out.println(ctx.status());
System.out.println();
System.out.println("c) cleanup inventory");
System.out.println("n) remove known nodes");
System.out.println("r) resend unacknowledged messages");
System.out.println(COMMAND_BACK);
@ -123,6 +124,9 @@ public class Application {
case "c":
ctx.cleanup();
break;
case "n":
ctx.internals().getNodeRegistry().cleanup();
break;
case "r":
ctx.resendUnacknowledgedMessages();
break;
@ -285,7 +289,7 @@ public class Application {
}
private void labels() {
List<Label> labels = ctx.messages().getLabels();
List<Label> labels = ctx.labels().getLabels();
String command;
do {
System.out.println();

View File

@ -24,6 +24,7 @@ import ch.dissem.bitmessage.ports.NodeRegistry;
import ch.dissem.bitmessage.repository.*;
import ch.dissem.bitmessage.wif.WifExporter;
import ch.dissem.bitmessage.wif.WifImporter;
import org.jetbrains.annotations.NotNull;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;
@ -62,18 +63,35 @@ public class Main {
.inventory(new JdbcInventory(jdbcConfig))
.messageRepo(new JdbcMessageRepository(jdbcConfig))
.powRepo(new JdbcProofOfWorkRepository(jdbcConfig))
.labelRepo(new JdbcLabelRepository(jdbcConfig))
.networkHandler(new NioNetworkHandler())
.cryptography(new BouncyCryptography())
.port(48444);
.cryptography(new BouncyCryptography());
ctxBuilder.getPreferences().setPort(48444);
if (options.localPort != null) {
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
public void clear() {
// NO OP
}
@NotNull
@Override
public List<NetworkAddress> getKnownAddresses(int limit, long... streams) {
public List<NetworkAddress> getKnownAddresses(int limit, @NotNull long... streams) {
return Arrays.stream(streams)
.mapToObj(s -> new NetworkAddress.Builder()
.ipv4(127, 0, 0, 1)
@ -83,7 +101,7 @@ public class Main {
}
@Override
public void offerAddresses(List<NetworkAddress> nodes) {
public void offerAddresses(@NotNull List<NetworkAddress> 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.ports.NodeRegistry;
import org.jetbrains.annotations.NotNull;
import java.util.LinkedList;
import java.util.List;
@ -44,13 +45,29 @@ class TestNodeRegistry implements NodeRegistry {
// NO OP
}
@NotNull
@Override
public List<NetworkAddress> getKnownAddresses(int limit, long... streams) {
public List<NetworkAddress> getKnownAddresses(int limit, @NotNull long... streams) {
return nodes;
}
@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
}
}

View File

@ -15,9 +15,10 @@ dependencies {
compile 'org.slf4j:slf4j-api'
compile 'com.beust:klaxon'
testCompile 'junit:junit:4.12'
testCompile 'org.hamcrest:hamcrest-library:1.3'
testCompile 'com.nhaarman:mockito-kotlin:1.5.0'
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(':cryptography-bc')
}

View File

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

View File

@ -19,10 +19,8 @@ package ch.dissem.bitmessage.exports
import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography
import ch.dissem.bitmessage.entity.BitmessageAddress
import ch.dissem.bitmessage.utils.TestUtils
import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.CoreMatchers.nullValue
import org.junit.Assert.assertThat
import org.junit.Test
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
class ContactExportTest {
@ -42,7 +40,7 @@ class ContactExportTest {
)
val export = ContactExport.exportContacts(contacts)
print(export.toJsonString(true))
assertThat(ContactExport.importContacts(export), `is`(contacts))
assertEquals(contacts, ContactExport.importContacts(export))
}
@Test
@ -53,9 +51,9 @@ class ContactExportTest {
val export = ContactExport.exportContacts(contacts)
print(export.toJsonString(true))
val import = ContactExport.importContacts(export)
assertThat(import.size, `is`(1))
assertThat(import[0].isChan, `is`(true))
assertThat(import[0].privateKey, `is`(nullValue()))
assertEquals(1, import.size)
assertTrue(import[0].isChan)
assertNull(import[0].privateKey)
}
@Test
@ -66,8 +64,9 @@ class ContactExportTest {
val export = ContactExport.exportContacts(contacts, true)
print(export.toJsonString(true))
val import = ContactExport.importContacts(export)
assertThat(import.size, `is`(1))
assertThat(import[0].isChan, `is`(true))
assertThat(import[0].privateKey, `is`(contacts[0].privateKey))
assertEquals(1, import.size)
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.Singleton
import ch.dissem.bitmessage.utils.TestUtils
import org.hamcrest.CoreMatchers.`is`
import org.junit.Assert.assertThat
import org.junit.Test
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class MessageExportTest {
val inbox = Label("Inbox", Label.Type.INBOX, 0x0000ff)
val outbox = Label("Outbox", Label.Type.OUTBOX, 0x00ff00)
val unread = Label("Unread", Label.Type.UNREAD, 0x000000)
val trash = Label("Trash", Label.Type.TRASH, 0x555555)
private val inbox = Label("Inbox", Label.Type.INBOX, 0x0000ff)
private val outbox = Label("Outbox", Label.Type.OUTBOX, 0x00ff00)
private val unread = Label("Unread", Label.Type.UNREAD, 0x000000)
private val trash = Label("Trash", Label.Type.TRASH, 0x555555)
val labels = listOf(
private val labels = listOf(
inbox,
outbox,
unread,
trash
)
val labelMap = MessageExport.createLabelMap(labels)
private val labelMap = MessageExport.createLabelMap(labels)
init {
TestUtils.mockedInternalContext(cryptography = BouncyCryptography())
@ -49,7 +48,7 @@ class MessageExportTest {
fun `ensure labels are exported`() {
val export = MessageExport.exportLabels(labels)
print(export.toJsonString(true))
assertThat(MessageExport.importLabels(export), `is`(labels))
assertEquals(labels, MessageExport.importLabels(export))
}
@Test
@ -84,6 +83,6 @@ class MessageExportTest {
)
val export = MessageExport.exportMessages(messages)
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 {
compile project(':core')
testCompile 'junit:junit'
testCompile 'org.slf4j:slf4j-simple'
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(':cryptography-bc')
}

View File

@ -19,6 +19,7 @@ package ch.dissem.bitmessage.extensions
import ch.dissem.bitmessage.entity.BitmessageAddress
import ch.dissem.bitmessage.entity.CustomMessage
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.Pubkey
import ch.dissem.bitmessage.exception.DecryptionFailedException
@ -41,19 +42,21 @@ import java.io.OutputStream
*/
class CryptoCustomMessage<T : Streamable> : CustomMessage {
private val dataReader: Reader<T>?
private val dataReader: (BitmessageAddress, InputStream) -> T
private var container: CryptoBox? = null
var sender: BitmessageAddress? = null
private set
private var data: T? = null
private set
constructor(data: T) : super(COMMAND, null) {
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.dataReader = dataReader
}
@ -73,52 +76,64 @@ class CryptoCustomMessage<T : Streamable> : CustomMessage {
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)
container = CryptoBox(out.toByteArray(), publicKey)
}
@Throws(DecryptionFailedException::class)
fun decrypt(privateKey: ByteArray): T {
val `in` = SignatureCheckingInputStream(container?.decrypt(privateKey) ?: throw IllegalStateException("no encrypted data available"))
if (dataReader == null) throw IllegalStateException("no data reader available")
val input = SignatureCheckingInputStream(
container?.decrypt(privateKey) ?: throw IllegalStateException("no encrypted data available")
)
val addressVersion = varInt(`in`)
val stream = varInt(`in`)
val behaviorBitfield = int32(`in`)
val publicSigningKey = bytes(`in`, 64)
val publicEncryptionKey = bytes(`in`, 64)
val nonceTrialsPerByte = if (addressVersion >= 3) varInt(`in`) else 0
val extraBytes = if (addressVersion >= 3) varInt(`in`) else 0
val addressVersion = varInt(input)
val stream = varInt(input)
val behaviorBitfield = int32(input)
val publicSigningKey = bytes(input, 64)
val publicEncryptionKey = bytes(input, 64)
val nonceTrialsPerByte = if (addressVersion >= 3) varInt(input) else 0
val extraBytes = if (addressVersion >= 3) varInt(input) else 0
val sender = BitmessageAddress(Factory.createPubkey(
addressVersion,
stream,
publicSigningKey,
publicEncryptionKey,
nonceTrialsPerByte,
extraBytes,
behaviorBitfield
))
val sender = BitmessageAddress(
Factory.createPubkey(
addressVersion,
stream,
publicSigningKey,
publicEncryptionKey,
nonceTrialsPerByte,
extraBytes,
behaviorBitfield
)
)
this.sender = sender
data = dataReader.read(sender, `in`)
data = dataReader.invoke(sender, input)
`in`.checkSignature(sender.pubkey!!)
input.checkSignature(sender.pubkey!!)
return data!!
}
override fun write(out: OutputStream) {
Encode.varString(COMMAND, out)
container?.write(out) ?: throw IllegalStateException("not encrypted yet")
override fun writer(): StreamableWriter = Writer(this)
private class Writer(
private val item: CryptoCustomMessage<*>
) : CustomMessage.Writer(item) {
override fun write(out: OutputStream) {
Encode.varString(COMMAND, out)
item.container?.writer()?.write(out) ?: throw IllegalStateException("not encrypted yet")
}
}
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()
override fun read(): Int {
@ -136,11 +151,24 @@ class CryptoCustomMessage<T : Streamable> : CustomMessage {
}
companion object {
@JvmField val COMMAND = "ENCRYPTED"
const val COMMAND = "ENCRYPTED"
@JvmStatic fun <T : Streamable> read(data: CustomMessage, dataReader: Reader<T>): CryptoCustomMessage<T> {
val cryptoBox = CryptoBox.read(ByteArrayInputStream(data.getData()), data.getData().size)
return CryptoCustomMessage(cryptoBox, dataReader)
}
@JvmStatic
fun <T : Streamable> read(data: CustomMessage, dataReader: Reader<T>): CryptoCustomMessage<T> =
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.Streamable
import ch.dissem.bitmessage.entity.StreamableWriter
import ch.dissem.bitmessage.extensions.CryptoCustomMessage
import ch.dissem.bitmessage.utils.Decode.bytes
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 {
override fun write(out: OutputStream) {
out.write(initialHash)
Encode.varString(request.name, out)
Encode.varBytes(data, out)
}
override fun writer(): StreamableWriter = Writer(this)
private class Writer(
private val item: ProofOfWorkRequest
) : StreamableWriter {
override fun write(out: OutputStream) {
out.write(item.initialHash)
Encode.varString(item.request.name, out)
Encode.varBytes(item.data, out)
}
override fun write(buffer: ByteBuffer) {
buffer.put(item.initialHash)
Encode.varString(item.request.name, buffer)
Encode.varBytes(item.data, buffer)
}
override fun write(buffer: ByteBuffer) {
buffer.put(initialHash)
Encode.varString(request.name, buffer)
Encode.varBytes(data, buffer)
}
class Reader(private val identity: BitmessageAddress) : CryptoCustomMessage.Reader<ProofOfWorkRequest> {
override fun read(sender: BitmessageAddress, `in`: InputStream): ProofOfWorkRequest {
return ProofOfWorkRequest.read(identity, `in`)
override fun read(sender: BitmessageAddress, input: InputStream): ProofOfWorkRequest {
return ProofOfWorkRequest.read(identity, input)
}
}
@ -78,12 +87,12 @@ data class ProofOfWorkRequest @JvmOverloads constructor(val sender: BitmessageAd
companion object {
@JvmStatic
fun read(client: BitmessageAddress, `in`: InputStream): ProofOfWorkRequest {
fun read(client: BitmessageAddress, input: InputStream): ProofOfWorkRequest {
return ProofOfWorkRequest(
client,
bytes(`in`, 64),
Request.valueOf(varString(`in`)),
varBytes(`in`)
bytes(input, 64),
Request.valueOf(varString(input)),
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.valueobject.PrivateKey
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.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.ByteArrayOutputStream
import java.io.InputStream
import ch.dissem.bitmessage.utils.Singleton.cryptography
import org.junit.Assert.assertEquals
class CryptoCustomMessageTest : TestBase() {
@Test
fun `ensure encrypt then decrypt yields same object`() {
@ -40,19 +38,21 @@ class CryptoCustomMessageTest : TestBase() {
val payloadBefore = GenericPayload(0, 1, cryptography().randomBytes(100))
val messageBefore = CryptoCustomMessage(payloadBefore)
messageBefore.signAndEncrypt(sendingIdentity, cryptography().createPublicKey(sendingIdentity.publicDecryptionKey))
messageBefore.signAndEncrypt(
sendingIdentity,
cryptography().createPublicKey(sendingIdentity.publicDecryptionKey)
)
val out = ByteArrayOutputStream()
messageBefore.write(out)
val `in` = ByteArrayInputStream(out.toByteArray())
messageBefore.writer().write(out)
val input = ByteArrayInputStream(out.toByteArray())
val customMessage = CustomMessage.read(`in`, out.size())
val customMessage = CustomMessage.read(input, out.size())
val messageAfter = CryptoCustomMessage.read(customMessage,
object : CryptoCustomMessage.Reader<GenericPayload> {
override fun read(sender: BitmessageAddress, `in`: InputStream): GenericPayload {
return GenericPayload.read(0, 1, `in`, 100)
}
})
object : CryptoCustomMessage.Reader<GenericPayload> {
override fun read(sender: BitmessageAddress, input: InputStream) =
GenericPayload.read(0, 1, input, 100)
})
val payloadAfter = messageAfter.decrypt(sendingIdentity.publicDecryptionKey)
assertEquals(payloadBefore, payloadAfter)
@ -63,20 +63,27 @@ class CryptoCustomMessageTest : TestBase() {
val privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey"))
val sendingIdentity = BitmessageAddress(privateKey)
val requestBefore = ProofOfWorkRequest(sendingIdentity, cryptography().randomBytes(64),
ProofOfWorkRequest.Request.CALCULATE)
val requestBefore = ProofOfWorkRequest(
sendingIdentity, cryptography().randomBytes(64),
ProofOfWorkRequest.Request.CALCULATE
)
val messageBefore = CryptoCustomMessage(requestBefore)
messageBefore.signAndEncrypt(sendingIdentity, cryptography().createPublicKey(sendingIdentity.publicDecryptionKey))
messageBefore.signAndEncrypt(
sendingIdentity,
cryptography().createPublicKey(sendingIdentity.publicDecryptionKey)
)
val out = ByteArrayOutputStream()
messageBefore.write(out)
val `in` = ByteArrayInputStream(out.toByteArray())
messageBefore.writer().write(out)
val input = ByteArrayInputStream(out.toByteArray())
val customMessage = CustomMessage.read(`in`, out.size())
val messageAfter = CryptoCustomMessage.read(customMessage,
ProofOfWorkRequest.Reader(sendingIdentity))
val customMessage = CustomMessage.read(input, out.size())
val messageAfter = CryptoCustomMessage.read(
customMessage,
ProofOfWorkRequest.Reader(sendingIdentity)
)
val requestAfter = messageAfter.decrypt(sendingIdentity.publicDecryptionKey)
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
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
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 {
compile project(':core')
testCompile 'junit:junit'
testCompile 'org.slf4j:slf4j-simple'
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(':cryptography-bc')
}

View File

@ -31,6 +31,7 @@ import org.slf4j.LoggerFactory
import java.io.IOException
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit
/**
* 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
lateinit var streams: LongArray
protected set
private set
@Volatile var state = State.CONNECTING
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
// Let's tweak these numbers a bit:
fun isExpired(): Boolean = when (state) {
State.CONNECTING -> io.lastUpdate < System.currentTimeMillis() - 20000
State.ACTIVE -> io.lastUpdate < System.currentTimeMillis() - 600000
State.CONNECTING -> io.lastUpdate < System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(9)
State.ACTIVE -> io.lastUpdate < System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(3)
State.DISCONNECTED -> true
}

View File

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

View File

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

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