Compare commits
23 Commits
develop
...
feature/re
Author | SHA1 | Date | |
---|---|---|---|
519f457476 | |||
fe9fa0ba2f | |||
37cda3df56 | |||
fafabf64a3 | |||
7b9694e660 | |||
ce86ab55c3 | |||
25e118b88e | |||
cbebc38579 | |||
b44a2f8809 | |||
c7c285a2c1 | |||
81fc50ec37 | |||
f1403bcd00 | |||
e9acb0071e | |||
c425298b67 | |||
681ea148db | |||
fab1c06135 | |||
b93f382ccd | |||
00e4461043 | |||
18f870a4cc | |||
278d5b05e6 | |||
ddb2073c2f | |||
a5c78fd8cf | |||
8cbdce6eac |
2
.gitignore
vendored
2
.gitignore
vendored
@ -49,7 +49,7 @@ gradle-app.setting
|
|||||||
## Plugin-specific files:
|
## Plugin-specific files:
|
||||||
|
|
||||||
# IntelliJ
|
# IntelliJ
|
||||||
/out/
|
out/
|
||||||
|
|
||||||
# mpeltonen/sbt-idea plugin
|
# mpeltonen/sbt-idea plugin
|
||||||
.idea_modules/
|
.idea_modules/
|
||||||
|
36
build.gradle
36
build.gradle
@ -1,5 +1,5 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.1.4-3'
|
ext.kotlin_version = '1.2.71'
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
@ -8,17 +8,17 @@ buildscript {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.github.ben-manes.versions' version '0.15.0'
|
id 'com.github.ben-manes.versions' version '0.17.0'
|
||||||
id "io.spring.dependency-management" version "1.0.3.RELEASE"
|
id "io.spring.dependency-management" version "1.0.4.RELEASE"
|
||||||
}
|
}
|
||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
|
apply plugin: 'io.spring.dependency-management'
|
||||||
apply plugin: 'kotlin'
|
apply plugin: 'kotlin'
|
||||||
apply plugin: 'maven'
|
apply plugin: 'maven'
|
||||||
apply plugin: 'signing'
|
apply plugin: 'signing'
|
||||||
apply plugin: 'jacoco'
|
apply plugin: 'jacoco'
|
||||||
apply plugin: 'gitflow-version'
|
apply plugin: 'gitflow-version'
|
||||||
apply plugin: 'io.spring.dependency-management'
|
|
||||||
apply plugin: 'com.github.ben-manes.versions'
|
apply plugin: 'com.github.ben-manes.versions'
|
||||||
|
|
||||||
sourceCompatibility = 1.7
|
sourceCompatibility = 1.7
|
||||||
@ -31,8 +31,8 @@ subprojects {
|
|||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7"
|
||||||
compile "org.jetbrains.kotlin:kotlin-reflect"
|
implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
@ -130,7 +130,7 @@ subprojects {
|
|||||||
dependencyManagement {
|
dependencyManagement {
|
||||||
dependencies {
|
dependencies {
|
||||||
dependencySet(group: 'org.jetbrains.kotlin', version: "$kotlin_version") {
|
dependencySet(group: 'org.jetbrains.kotlin', version: "$kotlin_version") {
|
||||||
entry 'kotlin-stdlib-jre7'
|
entry 'kotlin-stdlib-jdk7'
|
||||||
entry 'kotlin-reflect'
|
entry 'kotlin-reflect'
|
||||||
}
|
}
|
||||||
dependencySet(group: 'org.slf4j', version: '1.7.25') {
|
dependencySet(group: 'org.slf4j', version: '1.7.25') {
|
||||||
@ -138,20 +138,22 @@ subprojects {
|
|||||||
entry 'slf4j-simple'
|
entry 'slf4j-simple'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependency 'ch.dissem.msgpack:msgpack:2.0.0'
|
dependency 'ch.dissem.msgpack:msgpack:2.0.1'
|
||||||
dependency 'org.bouncycastle:bcprov-jdk15on:1.57'
|
dependency 'org.bouncycastle:bcprov-jdk15on:1.60'
|
||||||
dependency 'com.madgag.spongycastle:prov:1.56.0.0'
|
dependency 'com.madgag.spongycastle:prov:1.58.0.0'
|
||||||
dependency 'org.apache.commons:commons-lang3:3.6'
|
dependency 'org.apache.commons:commons-text:1.5'
|
||||||
dependency 'org.flywaydb:flyway-core:4.2.0'
|
dependency 'org.flywaydb:flyway-core:5.2.0'
|
||||||
dependency 'com.beust:klaxon:0.31'
|
dependency 'com.beust:klaxon:3.0.8'
|
||||||
|
|
||||||
dependency 'args4j:args4j:2.33'
|
dependency 'args4j:args4j:2.33'
|
||||||
dependency 'org.ini4j:ini4j:0.5.4'
|
dependency 'org.ini4j:ini4j:0.5.4'
|
||||||
dependency 'com.h2database:h2:1.4.196'
|
dependency 'com.h2database:h2:1.4.197'
|
||||||
|
|
||||||
dependency 'junit:junit:4.12'
|
dependency 'org.hamcrest:java-hamcrest:2.0.0.0'
|
||||||
dependency 'org.hamcrest:hamcrest-library:1.3'
|
dependency 'com.nhaarman:mockito-kotlin:1.6.0'
|
||||||
dependency 'com.nhaarman:mockito-kotlin:1.5.0'
|
|
||||||
|
dependency 'org.junit.jupiter:junit-jupiter-api:5.3.1'
|
||||||
|
dependency 'org.junit.jupiter:junit-jupiter-engine:5.3.1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,10 +25,10 @@ artifacts {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile 'org.slf4j:slf4j-api'
|
compile 'org.slf4j:slf4j-api'
|
||||||
compile 'ch.dissem.msgpack:msgpack:1.0.0'
|
compile 'ch.dissem.msgpack:msgpack'
|
||||||
testCompile 'junit:junit:4.12'
|
testCompile 'com.nhaarman:mockito-kotlin'
|
||||||
testCompile 'org.hamcrest:hamcrest-library:1.3'
|
testCompile 'org.junit.jupiter:junit-jupiter-api'
|
||||||
testCompile 'com.nhaarman:mockito-kotlin:1.5.0'
|
testRuntime 'org.junit.jupiter:junit-jupiter-engine'
|
||||||
testCompile project(':cryptography-bc')
|
testCompile project(':cryptography-bc')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package ch.dissem.bitmessage
|
package ch.dissem.bitmessage
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.BitmessageContext.Companion.version
|
||||||
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES
|
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES
|
||||||
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE
|
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
@ -33,7 +34,6 @@ import ch.dissem.bitmessage.exception.DecryptionFailedException
|
|||||||
import ch.dissem.bitmessage.factory.Factory
|
import ch.dissem.bitmessage.factory.Factory
|
||||||
import ch.dissem.bitmessage.ports.*
|
import ch.dissem.bitmessage.ports.*
|
||||||
import ch.dissem.bitmessage.utils.Property
|
import ch.dissem.bitmessage.utils.Property
|
||||||
import ch.dissem.bitmessage.utils.UnixTime.HOUR
|
|
||||||
import ch.dissem.bitmessage.utils.UnixTime.MINUTE
|
import ch.dissem.bitmessage.utils.UnixTime.MINUTE
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
@ -58,57 +58,7 @@ import kotlin.properties.Delegates
|
|||||||
*
|
*
|
||||||
* The port defaults to 8444 (the default Bitmessage port)
|
* The port defaults to 8444 (the default Bitmessage port)
|
||||||
*/
|
*/
|
||||||
class BitmessageContext(
|
class BitmessageContext private constructor(builder: BitmessageContext.Builder) {
|
||||||
cryptography: Cryptography,
|
|
||||||
inventory: Inventory,
|
|
||||||
nodeRegistry: NodeRegistry,
|
|
||||||
networkHandler: NetworkHandler,
|
|
||||||
addressRepository: AddressRepository,
|
|
||||||
messageRepository: MessageRepository,
|
|
||||||
proofOfWorkRepository: ProofOfWorkRepository,
|
|
||||||
proofOfWorkEngine: ProofOfWorkEngine = MultiThreadedPOWEngine(),
|
|
||||||
customCommandHandler: CustomCommandHandler = object : CustomCommandHandler {
|
|
||||||
override fun handle(request: CustomMessage): MessagePayload? {
|
|
||||||
BitmessageContext.LOG.debug("Received custom request, but no custom command handler configured.")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
listener: Listener,
|
|
||||||
labeler: Labeler = DefaultLabeler(),
|
|
||||||
userAgent: String? = null,
|
|
||||||
port: Int = 8444,
|
|
||||||
connectionTTL: Long = 30 * MINUTE,
|
|
||||||
connectionLimit: Int = 150,
|
|
||||||
sendPubkeyOnIdentityCreation: Boolean = true,
|
|
||||||
doMissingProofOfWorkDelayInSeconds: Int = 30
|
|
||||||
) {
|
|
||||||
|
|
||||||
private constructor(builder: BitmessageContext.Builder) : this(
|
|
||||||
builder.cryptography,
|
|
||||||
builder.inventory,
|
|
||||||
builder.nodeRegistry,
|
|
||||||
builder.networkHandler,
|
|
||||||
builder.addressRepo,
|
|
||||||
builder.messageRepo,
|
|
||||||
builder.proofOfWorkRepository,
|
|
||||||
builder.proofOfWorkEngine ?: MultiThreadedPOWEngine(),
|
|
||||||
builder.customCommandHandler ?: object : CustomCommandHandler {
|
|
||||||
override fun handle(request: CustomMessage): MessagePayload? {
|
|
||||||
BitmessageContext.LOG.debug("Received custom request, but no custom command handler configured.")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
builder.listener,
|
|
||||||
builder.labeler ?: DefaultLabeler(),
|
|
||||||
builder.userAgent,
|
|
||||||
builder.port,
|
|
||||||
builder.connectionTTL,
|
|
||||||
builder.connectionLimit,
|
|
||||||
builder.sendPubkeyOnIdentityCreation,
|
|
||||||
builder.doMissingProofOfWorkDelay
|
|
||||||
)
|
|
||||||
|
|
||||||
private val sendPubkeyOnIdentityCreation: Boolean
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [InternalContext] - normally you wouldn't need it,
|
* The [InternalContext] - normally you wouldn't need it,
|
||||||
@ -123,6 +73,9 @@ class BitmessageContext(
|
|||||||
val addresses: AddressRepository
|
val addresses: AddressRepository
|
||||||
@JvmName("addresses") get
|
@JvmName("addresses") get
|
||||||
|
|
||||||
|
val labels: LabelRepository
|
||||||
|
@JvmName("labels") get
|
||||||
|
|
||||||
val messages: MessageRepository
|
val messages: MessageRepository
|
||||||
@JvmName("messages") get
|
@JvmName("messages") get
|
||||||
|
|
||||||
@ -135,7 +88,7 @@ class BitmessageContext(
|
|||||||
*features
|
*features
|
||||||
))
|
))
|
||||||
internals.addressRepository.save(identity)
|
internals.addressRepository.save(identity)
|
||||||
if (sendPubkeyOnIdentityCreation) {
|
if (internals.preferences.sendPubkeyOnIdentityCreation) {
|
||||||
internals.sendPubkey(identity, identity.stream)
|
internals.sendPubkey(identity, identity.stream)
|
||||||
}
|
}
|
||||||
return identity
|
return identity
|
||||||
@ -262,9 +215,8 @@ class BitmessageContext(
|
|||||||
* @param request the request
|
* @param request the request
|
||||||
* @return the response
|
* @return the response
|
||||||
*/
|
*/
|
||||||
fun send(server: InetAddress, port: Int, request: CustomMessage): CustomMessage {
|
fun send(server: InetAddress, port: Int, request: CustomMessage): CustomMessage =
|
||||||
return internals.networkHandler.send(server, port, request)
|
internals.networkHandler.send(server, port, request)
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes expired objects from the inventory. You should call this method regularly,
|
* Removes expired objects from the inventory. You should call this method regularly,
|
||||||
@ -272,6 +224,7 @@ class BitmessageContext(
|
|||||||
*/
|
*/
|
||||||
fun cleanup() {
|
fun cleanup() {
|
||||||
internals.inventory.cleanup()
|
internals.inventory.cleanup()
|
||||||
|
internals.nodeRegistry.cleanup()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -326,7 +279,7 @@ class BitmessageContext(
|
|||||||
|
|
||||||
fun status(): Property {
|
fun status(): Property {
|
||||||
return Property("status",
|
return Property("status",
|
||||||
Property("user agent", internals.userAgent),
|
Property("user agent", internals.preferences.userAgent),
|
||||||
internals.networkHandler.getNetworkStatus(),
|
internals.networkHandler.getNetworkStatus(),
|
||||||
Property("unacknowledged", internals.messageRepository.findMessagesToResend().size)
|
Property("unacknowledged", internals.messageRepository.findMessagesToResend().size)
|
||||||
)
|
)
|
||||||
@ -343,29 +296,23 @@ class BitmessageContext(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kotlin users: you might want to use [BitmessageContext.build] instead.
|
||||||
|
*/
|
||||||
class Builder {
|
class Builder {
|
||||||
internal var port = 8444
|
var inventory by Delegates.notNull<Inventory>()
|
||||||
internal var inventory by Delegates.notNull<Inventory>()
|
var nodeRegistry by Delegates.notNull<NodeRegistry>()
|
||||||
internal var nodeRegistry by Delegates.notNull<NodeRegistry>()
|
var networkHandler by Delegates.notNull<NetworkHandler>()
|
||||||
internal var networkHandler by Delegates.notNull<NetworkHandler>()
|
var addressRepo by Delegates.notNull<AddressRepository>()
|
||||||
internal var addressRepo by Delegates.notNull<AddressRepository>()
|
var labelRepo by Delegates.notNull<LabelRepository>()
|
||||||
internal var messageRepo by Delegates.notNull<MessageRepository>()
|
var messageRepo by Delegates.notNull<MessageRepository>()
|
||||||
internal var proofOfWorkRepository by Delegates.notNull<ProofOfWorkRepository>()
|
var proofOfWorkRepo by Delegates.notNull<ProofOfWorkRepository>()
|
||||||
internal var proofOfWorkEngine: ProofOfWorkEngine? = null
|
var proofOfWorkEngine: ProofOfWorkEngine? = null
|
||||||
internal var cryptography by Delegates.notNull<Cryptography>()
|
var cryptography by Delegates.notNull<Cryptography>()
|
||||||
internal var customCommandHandler: CustomCommandHandler? = null
|
var customCommandHandler: CustomCommandHandler? = null
|
||||||
internal var labeler: Labeler? = null
|
var labeler: Labeler? = null
|
||||||
internal var userAgent: String? = null
|
var listener by Delegates.notNull<Listener>()
|
||||||
internal var listener by Delegates.notNull<Listener>()
|
val preferences = Preferences()
|
||||||
internal var connectionLimit = 150
|
|
||||||
internal var connectionTTL = 30 * MINUTE
|
|
||||||
internal var sendPubkeyOnIdentityCreation = true
|
|
||||||
internal var doMissingProofOfWorkDelay = 30
|
|
||||||
|
|
||||||
fun port(port: Int): Builder {
|
|
||||||
this.port = port
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun inventory(inventory: Inventory): Builder {
|
fun inventory(inventory: Inventory): Builder {
|
||||||
this.inventory = inventory
|
this.inventory = inventory
|
||||||
@ -387,13 +334,18 @@ class BitmessageContext(
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun labelRepo(labelRepo: LabelRepository): Builder {
|
||||||
|
this.labelRepo = labelRepo
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
fun messageRepo(messageRepo: MessageRepository): Builder {
|
fun messageRepo(messageRepo: MessageRepository): Builder {
|
||||||
this.messageRepo = messageRepo
|
this.messageRepo = messageRepo
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun powRepo(proofOfWorkRepository: ProofOfWorkRepository): Builder {
|
fun powRepo(proofOfWorkRepository: ProofOfWorkRepository): Builder {
|
||||||
this.proofOfWorkRepository = proofOfWorkRepository
|
this.proofOfWorkRepo = proofOfWorkRepository
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -422,7 +374,7 @@ class BitmessageContext(
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmName("kotlinListener")
|
@JvmSynthetic
|
||||||
fun listener(listener: (Plaintext) -> Unit): Builder {
|
fun listener(listener: (Plaintext) -> Unit): Builder {
|
||||||
this.listener = object : Listener {
|
this.listener = object : Listener {
|
||||||
override fun receive(plaintext: Plaintext) {
|
override fun receive(plaintext: Plaintext) {
|
||||||
@ -432,63 +384,41 @@ class BitmessageContext(
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun connectionLimit(connectionLimit: Int): Builder {
|
fun build() = BitmessageContext(this)
|
||||||
this.connectionLimit = connectionLimit
|
|
||||||
return this
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun connectionTTL(hours: Int): Builder {
|
|
||||||
this.connectionTTL = hours * HOUR
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun doMissingProofOfWorkDelay(seconds: Int) {
|
|
||||||
this.doMissingProofOfWorkDelay = seconds
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* By default a client will send the public key when an identity is being created. On weaker devices
|
|
||||||
* this behaviour might not be desirable.
|
|
||||||
*/
|
|
||||||
fun doNotSendPubkeyOnIdentityCreation(): Builder {
|
|
||||||
this.sendPubkeyOnIdentityCreation = false
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun build(): BitmessageContext {
|
|
||||||
return BitmessageContext(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
this.labeler = labeler
|
this.labeler = builder.labeler ?: DefaultLabeler()
|
||||||
this.internals = InternalContext(
|
this.internals = InternalContext(
|
||||||
cryptography,
|
builder.cryptography,
|
||||||
inventory,
|
builder.inventory,
|
||||||
nodeRegistry,
|
builder.nodeRegistry,
|
||||||
networkHandler,
|
builder.networkHandler,
|
||||||
addressRepository,
|
builder.addressRepo,
|
||||||
messageRepository,
|
builder.labelRepo,
|
||||||
proofOfWorkRepository,
|
builder.messageRepo,
|
||||||
proofOfWorkEngine,
|
builder.proofOfWorkRepo,
|
||||||
customCommandHandler,
|
builder.proofOfWorkEngine ?: MultiThreadedPOWEngine(),
|
||||||
listener,
|
builder.customCommandHandler ?: object : CustomCommandHandler {
|
||||||
|
override fun handle(request: CustomMessage): MessagePayload? {
|
||||||
|
BitmessageContext.LOG.debug("Received custom request, but no custom command handler configured.")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
builder.listener,
|
||||||
labeler,
|
labeler,
|
||||||
userAgent?.let { "/$it/Jabit:$version/" } ?: "/Jabit:$version/",
|
builder.preferences
|
||||||
port,
|
|
||||||
connectionTTL,
|
|
||||||
connectionLimit
|
|
||||||
)
|
)
|
||||||
this.addresses = addressRepository
|
this.addresses = builder.addressRepo
|
||||||
this.messages = messageRepository
|
this.labels = builder.labelRepo
|
||||||
this.sendPubkeyOnIdentityCreation = sendPubkeyOnIdentityCreation
|
this.messages = builder.messageRepo
|
||||||
(listener as? Listener.WithContext)?.setContext(this)
|
(builder.listener as? Listener.WithContext)?.setContext(this)
|
||||||
internals.proofOfWorkService.doMissingProofOfWork(doMissingProofOfWorkDelayInSeconds * 1000L)
|
internals.proofOfWorkService.doMissingProofOfWork(builder.preferences.doMissingProofOfWorkDelayInSeconds * 1000L)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmField val CURRENT_VERSION = 3
|
@JvmField
|
||||||
|
val CURRENT_VERSION = 3
|
||||||
private val LOG = LoggerFactory.getLogger(BitmessageContext::class.java)
|
private val LOG = LoggerFactory.getLogger(BitmessageContext::class.java)
|
||||||
|
|
||||||
val version: String by lazy {
|
val version: String by lazy {
|
||||||
@ -496,5 +426,40 @@ class BitmessageContext(
|
|||||||
}
|
}
|
||||||
@JvmStatic get
|
@JvmStatic get
|
||||||
|
|
||||||
|
@JvmSynthetic
|
||||||
|
inline fun build(block: Builder.() -> Unit): BitmessageContext {
|
||||||
|
val builder = Builder()
|
||||||
|
block(builder)
|
||||||
|
return builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Preferences {
|
||||||
|
var port = 8444
|
||||||
|
/**
|
||||||
|
* Defaults to "/Jabit:<version>/", and whatever you set will be inserted into "/<your user agent>/Jabit:<version>/"
|
||||||
|
*/
|
||||||
|
var userAgent = "/Jabit:$version/"
|
||||||
|
set(value) {
|
||||||
|
field = "/$value/Jabit:$version/"
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Time to live for any connection
|
||||||
|
*/
|
||||||
|
var connectionTTL = 30 * MINUTE
|
||||||
|
/**
|
||||||
|
* Maximum number of connections. Values below 8 would probably result in erratic behaviour, so you shouldn't do that.
|
||||||
|
*/
|
||||||
|
var connectionLimit = 150
|
||||||
|
/**
|
||||||
|
* By default a client will send the public key when an identity is being created. On weaker devices
|
||||||
|
* this behaviour might not be desirable.
|
||||||
|
*/
|
||||||
|
var sendPubkeyOnIdentityCreation = true
|
||||||
|
/**
|
||||||
|
* Delay in seconds before outstandinng proof of work is calculated.
|
||||||
|
*/
|
||||||
|
var doMissingProofOfWorkDelayInSeconds = 30
|
||||||
|
}
|
||||||
|
@ -40,22 +40,19 @@ import java.util.concurrent.Executors
|
|||||||
*/
|
*/
|
||||||
class InternalContext(
|
class InternalContext(
|
||||||
val cryptography: Cryptography,
|
val cryptography: Cryptography,
|
||||||
val inventory: ch.dissem.bitmessage.ports.Inventory,
|
val inventory: Inventory,
|
||||||
val nodeRegistry: NodeRegistry,
|
val nodeRegistry: NodeRegistry,
|
||||||
val networkHandler: NetworkHandler,
|
val networkHandler: NetworkHandler,
|
||||||
val addressRepository: AddressRepository,
|
val addressRepository: AddressRepository,
|
||||||
val messageRepository: ch.dissem.bitmessage.ports.MessageRepository,
|
val labelRepository: LabelRepository,
|
||||||
|
val messageRepository: MessageRepository,
|
||||||
val proofOfWorkRepository: ProofOfWorkRepository,
|
val proofOfWorkRepository: ProofOfWorkRepository,
|
||||||
val proofOfWorkEngine: ProofOfWorkEngine,
|
val proofOfWorkEngine: ProofOfWorkEngine,
|
||||||
val customCommandHandler: CustomCommandHandler,
|
val customCommandHandler: CustomCommandHandler,
|
||||||
listener: BitmessageContext.Listener,
|
listener: BitmessageContext.Listener,
|
||||||
val labeler: Labeler,
|
val labeler: Labeler,
|
||||||
|
|
||||||
val userAgent: String,
|
val preferences: Preferences
|
||||||
|
|
||||||
val port: Int,
|
|
||||||
val connectionTTL: Long,
|
|
||||||
val connectionLimit: Int
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val threadPool = Executors.newCachedThreadPool()
|
private val threadPool = Executors.newCachedThreadPool()
|
||||||
@ -220,7 +217,9 @@ class InternalContext(
|
|||||||
companion object {
|
companion object {
|
||||||
private val LOG = LoggerFactory.getLogger(InternalContext::class.java)
|
private val LOG = LoggerFactory.getLogger(InternalContext::class.java)
|
||||||
|
|
||||||
@JvmField val NETWORK_NONCE_TRIALS_PER_BYTE: Long = 1000
|
@JvmField
|
||||||
@JvmField val NETWORK_EXTRA_BYTES: Long = 1000
|
val NETWORK_NONCE_TRIALS_PER_BYTE: Long = 1000
|
||||||
|
@JvmField
|
||||||
|
val NETWORK_EXTRA_BYTES: Long = 1000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,19 +25,28 @@ import java.nio.ByteBuffer
|
|||||||
* The 'addr' command holds a list of known active Bitmessage nodes.
|
* The 'addr' command holds a list of known active Bitmessage nodes.
|
||||||
*/
|
*/
|
||||||
data class Addr constructor(val addresses: List<NetworkAddress>) : MessagePayload {
|
data class Addr constructor(val addresses: List<NetworkAddress>) : MessagePayload {
|
||||||
|
|
||||||
override val command: MessagePayload.Command = MessagePayload.Command.ADDR
|
override val command: MessagePayload.Command = MessagePayload.Command.ADDR
|
||||||
|
|
||||||
|
override fun writer(): StreamableWriter = Writer(this)
|
||||||
|
|
||||||
|
private class Writer(
|
||||||
|
private val item: Addr
|
||||||
|
) : StreamableWriter {
|
||||||
|
|
||||||
override fun write(out: OutputStream) {
|
override fun write(out: OutputStream) {
|
||||||
Encode.varInt(addresses.size, out)
|
Encode.varInt(item.addresses.size, out)
|
||||||
for (address in addresses) {
|
for (address in item.addresses) {
|
||||||
address.write(out)
|
address.writer().write(out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun write(buffer: ByteBuffer) {
|
override fun write(buffer: ByteBuffer) {
|
||||||
Encode.varInt(addresses.size, buffer)
|
Encode.varInt(item.addresses.size, buffer)
|
||||||
for (address in addresses) {
|
for (address in item.addresses) {
|
||||||
address.write(buffer)
|
address.writer().write(buffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,15 +116,15 @@ class BitmessageAddress : Serializable {
|
|||||||
constructor(address: String) {
|
constructor(address: String) {
|
||||||
this.address = address
|
this.address = address
|
||||||
val bytes = Base58.decode(address.substring(3))
|
val bytes = Base58.decode(address.substring(3))
|
||||||
val `in` = ByteArrayInputStream(bytes)
|
val input = ByteArrayInputStream(bytes)
|
||||||
val counter = AccessCounter()
|
val counter = AccessCounter()
|
||||||
this.version = varInt(`in`, counter)
|
this.version = varInt(input, counter)
|
||||||
this.stream = varInt(`in`, counter)
|
this.stream = varInt(input, counter)
|
||||||
this.ripe = Bytes.expand(bytes(`in`, bytes.size - counter.length() - 4), 20)
|
this.ripe = Bytes.expand(bytes(input, bytes.size - counter.length() - 4), 20)
|
||||||
|
|
||||||
// test checksum
|
// test checksum
|
||||||
var checksum = cryptography().doubleSha512(bytes, bytes.size - 4)
|
var checksum = cryptography().doubleSha512(bytes, bytes.size - 4)
|
||||||
val expectedChecksum = bytes(`in`, 4)
|
val expectedChecksum = bytes(input, 4)
|
||||||
for (i in 0..3) {
|
for (i in 0..3) {
|
||||||
if (expectedChecksum[i] != checksum[i])
|
if (expectedChecksum[i] != checksum[i])
|
||||||
throw IllegalArgumentException("Checksum of address failed")
|
throw IllegalArgumentException("Checksum of address failed")
|
||||||
|
@ -33,22 +33,26 @@ open class CustomMessage(val customCommand: String, private val data: ByteArray?
|
|||||||
|
|
||||||
override val command: MessagePayload.Command = MessagePayload.Command.CUSTOM
|
override val command: MessagePayload.Command = MessagePayload.Command.CUSTOM
|
||||||
|
|
||||||
val isError: Boolean
|
val isError = COMMAND_ERROR == customCommand
|
||||||
|
|
||||||
fun getData(): ByteArray {
|
fun getData(): ByteArray {
|
||||||
if (data != null) {
|
return data ?: {
|
||||||
return data
|
|
||||||
} else {
|
|
||||||
val out = ByteArrayOutputStream()
|
val out = ByteArrayOutputStream()
|
||||||
write(out)
|
writer().write(out)
|
||||||
return out.toByteArray()
|
out.toByteArray()
|
||||||
}
|
}.invoke()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun writer(): StreamableWriter = Writer(this)
|
||||||
|
|
||||||
|
protected open class Writer(
|
||||||
|
private val item: CustomMessage
|
||||||
|
) : StreamableWriter {
|
||||||
|
|
||||||
override fun write(out: OutputStream) {
|
override fun write(out: OutputStream) {
|
||||||
if (data != null) {
|
if (item.data != null) {
|
||||||
Encode.varString(customCommand, out)
|
Encode.varString(item.customCommand, out)
|
||||||
out.write(data)
|
out.write(item.data)
|
||||||
} else {
|
} else {
|
||||||
throw ApplicationException("Tried to write custom message without data. "
|
throw ApplicationException("Tried to write custom message without data. "
|
||||||
+ "Programmer: did you forget to override #write()?")
|
+ "Programmer: did you forget to override #write()?")
|
||||||
@ -56,31 +60,27 @@ open class CustomMessage(val customCommand: String, private val data: ByteArray?
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun write(buffer: ByteBuffer) {
|
override fun write(buffer: ByteBuffer) {
|
||||||
if (data != null) {
|
if (item.data != null) {
|
||||||
Encode.varString(customCommand, buffer)
|
Encode.varString(item.customCommand, buffer)
|
||||||
buffer.put(data)
|
buffer.put(item.data)
|
||||||
} else {
|
} else {
|
||||||
throw ApplicationException("Tried to write custom message without data. "
|
throw ApplicationException("Tried to write custom message without data. "
|
||||||
+ "Programmer: did you forget to override #write()?")
|
+ "Programmer: did you forget to override #write()?")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val COMMAND_ERROR = "ERROR"
|
val COMMAND_ERROR = "ERROR"
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun read(`in`: InputStream, length: Int): CustomMessage {
|
fun read(input: InputStream, length: Int): CustomMessage {
|
||||||
val counter = AccessCounter()
|
val counter = AccessCounter()
|
||||||
return CustomMessage(varString(`in`, counter), bytes(`in`, length - counter.length()))
|
return CustomMessage(varString(input, counter), bytes(input, length - counter.length()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun error(message: String): CustomMessage {
|
fun error(message: String) = CustomMessage(COMMAND_ERROR, message.toByteArray(charset("UTF-8")))
|
||||||
return CustomMessage(COMMAND_ERROR, message.toByteArray(charset("UTF-8")))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
this.isError = COMMAND_ERROR == customCommand
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,6 @@
|
|||||||
package ch.dissem.bitmessage.entity
|
package ch.dissem.bitmessage.entity
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
||||||
import ch.dissem.bitmessage.utils.Encode
|
|
||||||
import java.io.OutputStream
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The 'getdata' command is used to request objects from a node.
|
* The 'getdata' command is used to request objects from a node.
|
||||||
@ -28,21 +25,14 @@ class GetData constructor(var inventory: List<InventoryVector>) : MessagePayload
|
|||||||
|
|
||||||
override val command: MessagePayload.Command = MessagePayload.Command.GETDATA
|
override val command: MessagePayload.Command = MessagePayload.Command.GETDATA
|
||||||
|
|
||||||
override fun write(out: OutputStream) {
|
override fun writer(): StreamableWriter = Writer(this)
|
||||||
Encode.varInt(inventory.size, out)
|
|
||||||
for (iv in inventory) {
|
|
||||||
iv.write(out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun write(buffer: ByteBuffer) {
|
private class Writer(
|
||||||
Encode.varInt(inventory.size, buffer)
|
item: GetData
|
||||||
for (iv in inventory) {
|
) : InventoryWriter(item.inventory)
|
||||||
iv.write(buffer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmField val MAX_INVENTORY_SIZE = 50000
|
@JvmField
|
||||||
|
val MAX_INVENTORY_SIZE = 50000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,6 @@
|
|||||||
package ch.dissem.bitmessage.entity
|
package ch.dissem.bitmessage.entity
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
||||||
import ch.dissem.bitmessage.utils.Encode
|
|
||||||
import java.io.OutputStream
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The 'inv' command holds up to 50000 inventory vectors, i.e. hashes of inventory items.
|
* The 'inv' command holds up to 50000 inventory vectors, i.e. hashes of inventory items.
|
||||||
@ -28,17 +25,9 @@ class Inv constructor(val inventory: List<InventoryVector>) : MessagePayload {
|
|||||||
|
|
||||||
override val command: MessagePayload.Command = MessagePayload.Command.INV
|
override val command: MessagePayload.Command = MessagePayload.Command.INV
|
||||||
|
|
||||||
override fun write(out: OutputStream) {
|
override fun writer(): StreamableWriter = Writer(this)
|
||||||
Encode.varInt(inventory.size, out)
|
|
||||||
for (iv in inventory) {
|
|
||||||
iv.write(out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun write(buffer: ByteBuffer) {
|
private class Writer(
|
||||||
Encode.varInt(inventory.size, buffer)
|
item: Inv
|
||||||
for (iv in inventory) {
|
) : InventoryWriter(item.inventory)
|
||||||
iv.write(buffer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -18,7 +18,6 @@ package ch.dissem.bitmessage.entity
|
|||||||
|
|
||||||
import ch.dissem.bitmessage.utils.Encode
|
import ch.dissem.bitmessage.utils.Encode
|
||||||
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||||
import java.io.IOException
|
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
@ -32,26 +31,24 @@ data class NetworkMessage(
|
|||||||
val payload: MessagePayload
|
val payload: MessagePayload
|
||||||
) : Streamable {
|
) : Streamable {
|
||||||
|
|
||||||
/**
|
override fun writer(): Writer = Writer(this)
|
||||||
* First 4 bytes of sha512(payload)
|
|
||||||
*/
|
class Writer internal constructor(
|
||||||
private fun getChecksum(bytes: ByteArray): ByteArray {
|
private val item: NetworkMessage
|
||||||
val d = cryptography().sha512(bytes)
|
) : StreamableWriter {
|
||||||
return byteArrayOf(d[0], d[1], d[2], d[3])
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun write(out: OutputStream) {
|
override fun write(out: OutputStream) {
|
||||||
// magic
|
// magic
|
||||||
Encode.int32(MAGIC, out)
|
Encode.int32(MAGIC, out)
|
||||||
|
|
||||||
// ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected)
|
// ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected)
|
||||||
val command = payload.command.name.toLowerCase()
|
val command = item.payload.command.name.toLowerCase()
|
||||||
out.write(command.toByteArray(charset("ASCII")))
|
out.write(command.toByteArray(charset("ASCII")))
|
||||||
for (i in command.length..11) {
|
for (i in command.length..11) {
|
||||||
out.write(0x0)
|
out.write(0x0)
|
||||||
}
|
}
|
||||||
|
|
||||||
val payloadBytes = Encode.bytes(payload)
|
val payloadBytes = Encode.bytes(item.payload)
|
||||||
|
|
||||||
// Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would
|
// Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would
|
||||||
// ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are
|
// ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are
|
||||||
@ -94,14 +91,14 @@ data class NetworkMessage(
|
|||||||
Encode.int32(MAGIC, out)
|
Encode.int32(MAGIC, out)
|
||||||
|
|
||||||
// ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected)
|
// ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected)
|
||||||
val command = payload.command.name.toLowerCase()
|
val command = item.payload.command.name.toLowerCase()
|
||||||
out.put(command.toByteArray(charset("ASCII")))
|
out.put(command.toByteArray(charset("ASCII")))
|
||||||
|
|
||||||
for (i in command.length..11) {
|
for (i in command.length..11) {
|
||||||
out.put(0.toByte())
|
out.put(0.toByte())
|
||||||
}
|
}
|
||||||
|
|
||||||
val payloadBytes = Encode.bytes(payload)
|
val payloadBytes = Encode.bytes(item.payload)
|
||||||
|
|
||||||
// Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would
|
// Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would
|
||||||
// ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are
|
// ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are
|
||||||
@ -115,6 +112,16 @@ data class NetworkMessage(
|
|||||||
return payloadBytes
|
return payloadBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* First 4 bytes of sha512(payload)
|
||||||
|
*/
|
||||||
|
private fun getChecksum(bytes: ByteArray): ByteArray {
|
||||||
|
val d = cryptography().sha512(bytes)
|
||||||
|
return byteArrayOf(d[0], d[1], d[2], d[3])
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
* Magic value indicating message origin network, and used to seek to next message when stream state is unknown
|
* Magic value indicating message origin network, and used to seek to next message when stream state is unknown
|
||||||
|
@ -39,36 +39,26 @@ data class ObjectMessage(
|
|||||||
var nonce: ByteArray? = null,
|
var nonce: ByteArray? = null,
|
||||||
val expiresTime: Long,
|
val expiresTime: Long,
|
||||||
val payload: ObjectPayload,
|
val payload: ObjectPayload,
|
||||||
val type: Long,
|
val type: Long = payload.type?.number ?: throw IllegalArgumentException("payload must have type defined"),
|
||||||
/**
|
/**
|
||||||
* The object's version
|
* The object's version
|
||||||
*/
|
*/
|
||||||
val version: Long,
|
val version: Long = payload.version,
|
||||||
val stream: Long
|
val stream: Long = payload.stream
|
||||||
) : MessagePayload {
|
) : MessagePayload {
|
||||||
|
|
||||||
override val command: MessagePayload.Command = MessagePayload.Command.OBJECT
|
override val command: MessagePayload.Command = MessagePayload.Command.OBJECT
|
||||||
|
|
||||||
constructor(
|
|
||||||
nonce: ByteArray? = null,
|
|
||||||
expiresTime: Long,
|
|
||||||
payload: ObjectPayload,
|
|
||||||
stream: Long
|
|
||||||
) : this(
|
|
||||||
nonce,
|
|
||||||
expiresTime,
|
|
||||||
payload,
|
|
||||||
payload.type?.number ?: throw IllegalArgumentException("payload must have type defined"),
|
|
||||||
payload.version,
|
|
||||||
stream
|
|
||||||
)
|
|
||||||
|
|
||||||
val inventoryVector: InventoryVector
|
val inventoryVector: InventoryVector
|
||||||
get() {
|
get() {
|
||||||
return InventoryVector(Bytes.truncate(cryptography().doubleSha512(
|
return InventoryVector(
|
||||||
|
Bytes.truncate(
|
||||||
|
cryptography().doubleSha512(
|
||||||
nonce ?: throw IllegalStateException("nonce must be set"),
|
nonce ?: throw IllegalStateException("nonce must be set"),
|
||||||
payloadBytesWithoutNonce
|
payloadBytesWithoutNonce
|
||||||
), 32))
|
), 32
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val isEncrypted: Boolean
|
private val isEncrypted: Boolean
|
||||||
@ -81,8 +71,8 @@ data class ObjectMessage(
|
|||||||
get() {
|
get() {
|
||||||
try {
|
try {
|
||||||
val out = ByteArrayOutputStream()
|
val out = ByteArrayOutputStream()
|
||||||
writeHeaderWithoutNonce(out)
|
writer.writeHeaderWithoutNonce(out)
|
||||||
payload.writeBytesToSign(out)
|
payload.writer().writeBytesToSign(out)
|
||||||
return out.toByteArray()
|
return out.toByteArray()
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
throw ApplicationException(e)
|
throw ApplicationException(e)
|
||||||
@ -131,28 +121,37 @@ data class ObjectMessage(
|
|||||||
return cryptography().isSignatureValid(bytesToSign, payload.signature ?: return false, pubkey)
|
return cryptography().isSignatureValid(bytesToSign, payload.signature ?: return false, pubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val payloadBytesWithoutNonce: ByteArray by lazy {
|
||||||
|
val out = ByteArrayOutputStream()
|
||||||
|
writer.writeHeaderWithoutNonce(out)
|
||||||
|
payload.writer().write(out)
|
||||||
|
out.toByteArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val writer = Writer(this)
|
||||||
|
override fun writer(): StreamableWriter = writer
|
||||||
|
|
||||||
|
private class Writer(
|
||||||
|
private val item: ObjectMessage
|
||||||
|
) : StreamableWriter {
|
||||||
|
|
||||||
override fun write(out: OutputStream) {
|
override fun write(out: OutputStream) {
|
||||||
out.write(nonce ?: ByteArray(8))
|
out.write(item.nonce ?: ByteArray(8))
|
||||||
out.write(payloadBytesWithoutNonce)
|
out.write(item.payloadBytesWithoutNonce)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun write(buffer: ByteBuffer) {
|
override fun write(buffer: ByteBuffer) {
|
||||||
buffer.put(nonce ?: ByteArray(8))
|
buffer.put(item.nonce ?: ByteArray(8))
|
||||||
buffer.put(payloadBytesWithoutNonce)
|
buffer.put(item.payloadBytesWithoutNonce)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun writeHeaderWithoutNonce(out: OutputStream) {
|
internal fun writeHeaderWithoutNonce(out: OutputStream) {
|
||||||
Encode.int64(expiresTime, out)
|
Encode.int64(item.expiresTime, out)
|
||||||
Encode.int32(type, out)
|
Encode.int32(item.type, out)
|
||||||
Encode.varInt(version, out)
|
Encode.varInt(item.version, out)
|
||||||
Encode.varInt(stream, out)
|
Encode.varInt(item.stream, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
val payloadBytesWithoutNonce: ByteArray by lazy {
|
|
||||||
val out = ByteArrayOutputStream()
|
|
||||||
writeHeaderWithoutNonce(out)
|
|
||||||
payload.write(out)
|
|
||||||
out.toByteArray()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Builder {
|
class Builder {
|
||||||
|
@ -35,6 +35,7 @@ import java.nio.ByteBuffer
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
import kotlin.collections.HashSet
|
import kotlin.collections.HashSet
|
||||||
|
import kotlin.collections.LinkedHashSet
|
||||||
|
|
||||||
private fun message(encoding: Plaintext.Encoding, subject: String, body: String): ByteArray = when (encoding) {
|
private fun message(encoding: Plaintext.Encoding, subject: String, body: String): ByteArray = when (encoding) {
|
||||||
SIMPLE -> "Subject:$subject\nBody:$body".toByteArray()
|
SIMPLE -> "Subject:$subject\nBody:$body".toByteArray()
|
||||||
@ -64,7 +65,7 @@ class Plaintext private constructor(
|
|||||||
val message: ByteArray,
|
val message: ByteArray,
|
||||||
val ackData: ByteArray?,
|
val ackData: ByteArray?,
|
||||||
ackMessage: Lazy<ObjectMessage?> = lazy { Factory.createAck(from, ackData, ttl) },
|
ackMessage: Lazy<ObjectMessage?> = lazy { Factory.createAck(from, ackData, ttl) },
|
||||||
val conversationId: UUID = UUID.randomUUID(),
|
var conversationId: UUID = UUID.randomUUID(),
|
||||||
var inventoryVector: InventoryVector? = null,
|
var inventoryVector: InventoryVector? = null,
|
||||||
var signature: ByteArray? = null,
|
var signature: ByteArray? = null,
|
||||||
sent: Long? = null,
|
sent: Long? = null,
|
||||||
@ -86,10 +87,10 @@ class Plaintext private constructor(
|
|||||||
if (to == null) {
|
if (to == null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (this.to != null) {
|
this.to?.let {
|
||||||
if (this.to!!.version != 0L)
|
if (it.version != 0L)
|
||||||
throw IllegalStateException("Correct address already set")
|
throw IllegalStateException("Correct address already set")
|
||||||
if (!Arrays.equals(this.to!!.ripe, to.ripe)) {
|
if (!Arrays.equals(it.ripe, to.ripe)) {
|
||||||
throw IllegalArgumentException("RIPEs don't match")
|
throw IllegalArgumentException("RIPEs don't match")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -186,7 +187,8 @@ class Plaintext private constructor(
|
|||||||
Factory.getObjectMessage(
|
Factory.getObjectMessage(
|
||||||
3,
|
3,
|
||||||
ByteArrayInputStream(ackMessage),
|
ByteArrayInputStream(ackMessage),
|
||||||
ackMessage.size)
|
ackMessage.size
|
||||||
|
)
|
||||||
} else null
|
} else null
|
||||||
},
|
},
|
||||||
conversationId = conversationId,
|
conversationId = conversationId,
|
||||||
@ -242,7 +244,8 @@ class Plaintext private constructor(
|
|||||||
Factory.getObjectMessage(
|
Factory.getObjectMessage(
|
||||||
3,
|
3,
|
||||||
ByteArrayInputStream(ackMsg),
|
ByteArrayInputStream(ackMsg),
|
||||||
ackMsg.size)
|
ackMsg.size
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
Factory.createAck(builder.from!!, builder.ackData, builder.ttl)
|
Factory.createAck(builder.from!!, builder.ackData, builder.ttl)
|
||||||
}
|
}
|
||||||
@ -254,108 +257,12 @@ class Plaintext private constructor(
|
|||||||
received = builder.received,
|
received = builder.received,
|
||||||
initialHash = null,
|
initialHash = null,
|
||||||
ttl = builder.ttl,
|
ttl = builder.ttl,
|
||||||
labels = builder.labels,
|
labels = LinkedHashSet(builder.labels),
|
||||||
status = builder.status ?: Status.RECEIVED
|
status = builder.status ?: Status.RECEIVED
|
||||||
) {
|
) {
|
||||||
id = builder.id
|
id = builder.id
|
||||||
}
|
}
|
||||||
|
|
||||||
fun write(out: OutputStream, includeSignature: Boolean) {
|
|
||||||
Encode.varInt(from.version, out)
|
|
||||||
Encode.varInt(from.stream, out)
|
|
||||||
from.pubkey?.apply {
|
|
||||||
Encode.int32(behaviorBitfield, out)
|
|
||||||
out.write(signingKey, 1, 64)
|
|
||||||
out.write(encryptionKey, 1, 64)
|
|
||||||
if (from.version >= 3) {
|
|
||||||
Encode.varInt(nonceTrialsPerByte, out)
|
|
||||||
Encode.varInt(extraBytes, out)
|
|
||||||
}
|
|
||||||
} ?: {
|
|
||||||
Encode.int32(0, out)
|
|
||||||
val empty = ByteArray(64)
|
|
||||||
out.write(empty)
|
|
||||||
out.write(empty)
|
|
||||||
if (from.version >= 3) {
|
|
||||||
Encode.varInt(0, out)
|
|
||||||
Encode.varInt(0, out)
|
|
||||||
}
|
|
||||||
}.invoke()
|
|
||||||
if (type == MSG) {
|
|
||||||
out.write(to?.ripe ?: throw IllegalStateException("No recipient set for message"))
|
|
||||||
}
|
|
||||||
Encode.varInt(encodingCode, out)
|
|
||||||
Encode.varInt(message.size, out)
|
|
||||||
out.write(message)
|
|
||||||
if (type == MSG) {
|
|
||||||
if (to?.has(Feature.DOES_ACK) ?: false) {
|
|
||||||
val ack = ByteArrayOutputStream()
|
|
||||||
ackMessage?.write(ack)
|
|
||||||
Encode.varBytes(ack.toByteArray(), out)
|
|
||||||
} else {
|
|
||||||
Encode.varInt(0, out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (includeSignature) {
|
|
||||||
if (signature == null) {
|
|
||||||
Encode.varInt(0, out)
|
|
||||||
} else {
|
|
||||||
Encode.varBytes(signature!!, out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun write(buffer: ByteBuffer, includeSignature: Boolean) {
|
|
||||||
Encode.varInt(from.version, buffer)
|
|
||||||
Encode.varInt(from.stream, buffer)
|
|
||||||
if (from.pubkey == null) {
|
|
||||||
Encode.int32(0, buffer)
|
|
||||||
val empty = ByteArray(64)
|
|
||||||
buffer.put(empty)
|
|
||||||
buffer.put(empty)
|
|
||||||
if (from.version >= 3) {
|
|
||||||
Encode.varInt(0, buffer)
|
|
||||||
Encode.varInt(0, buffer)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Encode.int32(from.pubkey!!.behaviorBitfield, buffer)
|
|
||||||
buffer.put(from.pubkey!!.signingKey, 1, 64)
|
|
||||||
buffer.put(from.pubkey!!.encryptionKey, 1, 64)
|
|
||||||
if (from.version >= 3) {
|
|
||||||
Encode.varInt(from.pubkey!!.nonceTrialsPerByte, buffer)
|
|
||||||
Encode.varInt(from.pubkey!!.extraBytes, buffer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (type == MSG) {
|
|
||||||
buffer.put(to!!.ripe)
|
|
||||||
}
|
|
||||||
Encode.varInt(encodingCode, buffer)
|
|
||||||
Encode.varBytes(message, buffer)
|
|
||||||
if (type == MSG) {
|
|
||||||
if (to!!.has(Feature.DOES_ACK) && ackMessage != null) {
|
|
||||||
Encode.varBytes(Encode.bytes(ackMessage!!), buffer)
|
|
||||||
} else {
|
|
||||||
Encode.varInt(0, buffer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (includeSignature) {
|
|
||||||
val sig = signature
|
|
||||||
if (sig == null) {
|
|
||||||
Encode.varInt(0, buffer)
|
|
||||||
} else {
|
|
||||||
Encode.varBytes(sig, buffer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun write(out: OutputStream) {
|
|
||||||
write(out, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun write(buffer: ByteBuffer) {
|
|
||||||
write(buffer, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateNextTry() {
|
fun updateNextTry() {
|
||||||
if (to != null) {
|
if (to != null) {
|
||||||
if (nextTry == null) {
|
if (nextTry == null) {
|
||||||
@ -374,28 +281,30 @@ class Plaintext private constructor(
|
|||||||
get() {
|
get() {
|
||||||
val s = Scanner(ByteArrayInputStream(message), "UTF-8")
|
val s = Scanner(ByteArrayInputStream(message), "UTF-8")
|
||||||
val firstLine = s.nextLine()
|
val firstLine = s.nextLine()
|
||||||
if (encodingCode == EXTENDED.code) {
|
return when (encodingCode) {
|
||||||
if (Message.TYPE == extendedData?.type) {
|
EXTENDED.code -> if (Message.TYPE == extendedData?.type) {
|
||||||
return (extendedData!!.content as? Message)?.subject
|
(extendedData!!.content as? Message)?.subject
|
||||||
} else {
|
} else {
|
||||||
return null
|
null
|
||||||
}
|
}
|
||||||
} else if (encodingCode == SIMPLE.code) {
|
SIMPLE.code -> firstLine.substring("Subject:".length).trim { it <= ' ' }
|
||||||
return firstLine.substring("Subject:".length).trim { it <= ' ' }
|
else -> {
|
||||||
} else if (firstLine.length > 50) {
|
if (firstLine.length > 50) {
|
||||||
return firstLine.substring(0, 50).trim { it <= ' ' } + "..."
|
firstLine.substring(0, 50).trim { it <= ' ' } + "..."
|
||||||
} else {
|
} else {
|
||||||
return firstLine
|
firstLine
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val text: String?
|
val text: String?
|
||||||
get() {
|
get() {
|
||||||
if (encodingCode == EXTENDED.code) {
|
if (encodingCode == EXTENDED.code) {
|
||||||
if (Message.TYPE == extendedData?.type) {
|
return if (Message.TYPE == extendedData?.type) {
|
||||||
return (extendedData?.content as Message?)?.body
|
(extendedData?.content as Message?)?.body
|
||||||
} else {
|
} else {
|
||||||
return null
|
null
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val text = String(message)
|
val text = String(message)
|
||||||
@ -418,20 +327,20 @@ class Plaintext private constructor(
|
|||||||
val parents: List<InventoryVector>
|
val parents: List<InventoryVector>
|
||||||
get() {
|
get() {
|
||||||
val extendedData = extendedData ?: return emptyList()
|
val extendedData = extendedData ?: return emptyList()
|
||||||
if (Message.TYPE == extendedData.type) {
|
return if (Message.TYPE == extendedData.type) {
|
||||||
return (extendedData.content as Message).parents
|
(extendedData.content as Message).parents
|
||||||
} else {
|
} else {
|
||||||
return emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val files: List<Attachment>
|
val files: List<Attachment>
|
||||||
get() {
|
get() {
|
||||||
val extendedData = extendedData ?: return emptyList()
|
val extendedData = extendedData ?: return emptyList()
|
||||||
if (Message.TYPE == extendedData.type) {
|
return if (Message.TYPE == extendedData.type) {
|
||||||
return (extendedData.content as Message).files
|
(extendedData.content as Message).files
|
||||||
} else {
|
} else {
|
||||||
return emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -474,8 +383,8 @@ class Plaintext private constructor(
|
|||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
val subject = subject
|
val subject = subject
|
||||||
if (subject?.isNotEmpty() ?: false) {
|
if (subject?.isNotEmpty() == true) {
|
||||||
return subject!!
|
return subject
|
||||||
} else {
|
} else {
|
||||||
return Strings.hex(
|
return Strings.hex(
|
||||||
initialHash ?: return super.toString()
|
initialHash ?: return super.toString()
|
||||||
@ -484,6 +393,7 @@ class Plaintext private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum class Encoding constructor(code: Long) {
|
enum class Encoding constructor(code: Long) {
|
||||||
|
|
||||||
IGNORE(0), TRIVIAL(1), SIMPLE(2), EXTENDED(3);
|
IGNORE(0), TRIVIAL(1), SIMPLE(2), EXTENDED(3);
|
||||||
|
|
||||||
var code: Long = 0
|
var code: Long = 0
|
||||||
@ -495,7 +405,8 @@ class Plaintext private constructor(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
@JvmStatic fun fromCode(code: Long): Encoding? {
|
@JvmStatic
|
||||||
|
fun fromCode(code: Long): Encoding? {
|
||||||
for (e in values()) {
|
for (e in values()) {
|
||||||
if (e.code == code) {
|
if (e.code == code) {
|
||||||
return e
|
return e
|
||||||
@ -503,12 +414,13 @@ class Plaintext private constructor(
|
|||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Status {
|
enum class Status {
|
||||||
|
|
||||||
DRAFT,
|
DRAFT,
|
||||||
// For sent messages
|
|
||||||
PUBKEY_REQUESTED,
|
PUBKEY_REQUESTED,
|
||||||
DOING_PROOF_OF_WORK,
|
DOING_PROOF_OF_WORK,
|
||||||
SENT,
|
SENT,
|
||||||
@ -517,36 +429,158 @@ class Plaintext private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum class Type {
|
enum class Type {
|
||||||
|
|
||||||
MSG, BROADCAST
|
MSG, BROADCAST
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun writer(includeSignature: Boolean): StreamableWriter = Writer(this, includeSignature)
|
||||||
|
|
||||||
|
override fun writer(): StreamableWriter = Writer(this)
|
||||||
|
|
||||||
|
private class Writer(
|
||||||
|
private val item: Plaintext,
|
||||||
|
private val includeSignature: Boolean = true
|
||||||
|
) : StreamableWriter {
|
||||||
|
|
||||||
|
override fun write(out: OutputStream) {
|
||||||
|
Encode.varInt(item.from.version, out)
|
||||||
|
Encode.varInt(item.from.stream, out)
|
||||||
|
item.from.pubkey?.apply {
|
||||||
|
Encode.int32(behaviorBitfield, out)
|
||||||
|
out.write(signingKey, 1, 64)
|
||||||
|
out.write(encryptionKey, 1, 64)
|
||||||
|
if (item.from.version >= 3) {
|
||||||
|
Encode.varInt(nonceTrialsPerByte, out)
|
||||||
|
Encode.varInt(extraBytes, out)
|
||||||
|
}
|
||||||
|
} ?: {
|
||||||
|
Encode.int32(0, out)
|
||||||
|
val empty = ByteArray(64)
|
||||||
|
out.write(empty)
|
||||||
|
out.write(empty)
|
||||||
|
if (item.from.version >= 3) {
|
||||||
|
Encode.varInt(0, out)
|
||||||
|
Encode.varInt(0, out)
|
||||||
|
}
|
||||||
|
}.invoke()
|
||||||
|
if (item.type == MSG) {
|
||||||
|
// A draft without recipient is allowed, therefore this workaround.
|
||||||
|
item.to?.let { out.write(it.ripe) } ?: if (item.status == Status.DRAFT) {
|
||||||
|
out.write(ByteArray(20))
|
||||||
|
} else {
|
||||||
|
throw IllegalStateException("No recipient set for message")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Encode.varInt(item.encodingCode, out)
|
||||||
|
Encode.varInt(item.message.size, out)
|
||||||
|
out.write(item.message)
|
||||||
|
if (item.type == MSG) {
|
||||||
|
if (item.to?.has(Feature.DOES_ACK) == true) {
|
||||||
|
val ack = ByteArrayOutputStream()
|
||||||
|
item.ackMessage?.writer()?.write(ack)
|
||||||
|
Encode.varBytes(ack.toByteArray(), out)
|
||||||
|
} else {
|
||||||
|
Encode.varInt(0, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (includeSignature) {
|
||||||
|
val sig = item.signature
|
||||||
|
if (sig == null) {
|
||||||
|
Encode.varInt(0, out)
|
||||||
|
} else {
|
||||||
|
Encode.varBytes(sig, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(buffer: ByteBuffer) {
|
||||||
|
Encode.varInt(item.from.version, buffer)
|
||||||
|
Encode.varInt(item.from.stream, buffer)
|
||||||
|
if (item.from.pubkey == null) {
|
||||||
|
Encode.int32(0, buffer)
|
||||||
|
val empty = ByteArray(64)
|
||||||
|
buffer.put(empty)
|
||||||
|
buffer.put(empty)
|
||||||
|
if (item.from.version >= 3) {
|
||||||
|
Encode.varInt(0, buffer)
|
||||||
|
Encode.varInt(0, buffer)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Encode.int32(item.from.pubkey!!.behaviorBitfield, buffer)
|
||||||
|
buffer.put(item.from.pubkey!!.signingKey, 1, 64)
|
||||||
|
buffer.put(item.from.pubkey!!.encryptionKey, 1, 64)
|
||||||
|
if (item.from.version >= 3) {
|
||||||
|
Encode.varInt(item.from.pubkey!!.nonceTrialsPerByte, buffer)
|
||||||
|
Encode.varInt(item.from.pubkey!!.extraBytes, buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (item.type == MSG) {
|
||||||
|
// A draft without recipient is allowed, therefore this workaround.
|
||||||
|
item.to?.let { buffer.put(it.ripe) } ?: if (item.status == Status.DRAFT) {
|
||||||
|
buffer.put(ByteArray(20))
|
||||||
|
} else {
|
||||||
|
throw IllegalStateException("No recipient set for message")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Encode.varInt(item.encodingCode, buffer)
|
||||||
|
Encode.varBytes(item.message, buffer)
|
||||||
|
if (item.type == MSG) {
|
||||||
|
if (item.to!!.has(Feature.DOES_ACK) && item.ackMessage != null) {
|
||||||
|
Encode.varBytes(Encode.bytes(item.ackMessage!!), buffer)
|
||||||
|
} else {
|
||||||
|
Encode.varInt(0, buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (includeSignature) {
|
||||||
|
val sig = item.signature
|
||||||
|
if (sig == null) {
|
||||||
|
Encode.varInt(0, buffer)
|
||||||
|
} else {
|
||||||
|
Encode.varBytes(sig, buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
class Builder(internal val type: Type) {
|
class Builder(internal val type: Type) {
|
||||||
internal var id: Any? = null
|
var id: Any? = null
|
||||||
internal var inventoryVector: InventoryVector? = null
|
var inventoryVector: InventoryVector? = null
|
||||||
internal var from: BitmessageAddress? = null
|
var from: BitmessageAddress? = null
|
||||||
internal var to: BitmessageAddress? = null
|
var to: BitmessageAddress? = null
|
||||||
private var addressVersion: Long = 0
|
set(value) {
|
||||||
private var stream: Long = 0
|
if (value != null) {
|
||||||
private var behaviorBitfield: Int = 0
|
if (type != MSG && to != null)
|
||||||
private var publicSigningKey: ByteArray? = null
|
throw IllegalArgumentException("recipient address only allowed for msg")
|
||||||
private var publicEncryptionKey: ByteArray? = null
|
field = value
|
||||||
private var nonceTrialsPerByte: Long = 0
|
}
|
||||||
private var extraBytes: Long = 0
|
}
|
||||||
private var destinationRipe: ByteArray? = null
|
var addressVersion: Long = 0
|
||||||
private var preventAck: Boolean = false
|
var stream: Long = 0
|
||||||
internal var encoding: Long = 0
|
var behaviorBitfield: Int = 0
|
||||||
internal var message = ByteArray(0)
|
var publicSigningKey: ByteArray? = null
|
||||||
internal var ackData: ByteArray? = null
|
var publicEncryptionKey: ByteArray? = null
|
||||||
internal var ackMessage: ByteArray? = null
|
var nonceTrialsPerByte: Long = 0
|
||||||
internal var signature: ByteArray? = null
|
var extraBytes: Long = 0
|
||||||
internal var sent: Long? = null
|
var destinationRipe: ByteArray? = null
|
||||||
internal var received: Long? = null
|
set(value) {
|
||||||
internal var status: Status? = null
|
if (type != MSG && value != null) throw IllegalArgumentException("ripe only allowed for msg")
|
||||||
internal val labels = LinkedHashSet<Label>()
|
field = value
|
||||||
internal var ttl: Long = 0
|
}
|
||||||
internal var retries: Int = 0
|
var preventAck: Boolean = false
|
||||||
internal var nextTry: Long? = null
|
var encoding: Long = 0
|
||||||
internal var conversation: UUID? = null
|
var message = ByteArray(0)
|
||||||
|
var ackData: ByteArray? = null
|
||||||
|
var ackMessage: ByteArray? = null
|
||||||
|
var signature: ByteArray? = null
|
||||||
|
var sent: Long? = null
|
||||||
|
var received: Long? = null
|
||||||
|
var status: Status? = null
|
||||||
|
var labels: Collection<Label> = emptySet()
|
||||||
|
var ttl: Long = 0
|
||||||
|
var retries: Int = 0
|
||||||
|
var nextTry: Long? = null
|
||||||
|
var conversation: UUID? = null
|
||||||
|
|
||||||
fun id(id: Any): Builder {
|
fun id(id: Any): Builder {
|
||||||
this.id = id
|
this.id = id
|
||||||
@ -564,11 +598,7 @@ class Plaintext private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun to(address: BitmessageAddress?): Builder {
|
fun to(address: BitmessageAddress?): Builder {
|
||||||
if (address != null) {
|
|
||||||
if (type != MSG && to != null)
|
|
||||||
throw IllegalArgumentException("recipient address only allowed for msg")
|
|
||||||
to = address
|
to = address
|
||||||
}
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -608,7 +638,6 @@ class Plaintext private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun destinationRipe(ripe: ByteArray?): Builder {
|
fun destinationRipe(ripe: ByteArray?): Builder {
|
||||||
if (type != MSG && ripe != null) throw IllegalArgumentException("ripe only allowed for msg")
|
|
||||||
this.destinationRipe = ripe
|
this.destinationRipe = ripe
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
@ -684,7 +713,7 @@ class Plaintext private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun labels(labels: Collection<Label>): Builder {
|
fun labels(labels: Collection<Label>): Builder {
|
||||||
this.labels.addAll(labels)
|
this.labels = labels
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -710,7 +739,8 @@ class Plaintext private constructor(
|
|||||||
|
|
||||||
internal fun prepare(): Builder {
|
internal fun prepare(): Builder {
|
||||||
if (from == null) {
|
if (from == null) {
|
||||||
from = BitmessageAddress(Factory.createPubkey(
|
from = BitmessageAddress(
|
||||||
|
Factory.createPubkey(
|
||||||
addressVersion,
|
addressVersion,
|
||||||
stream,
|
stream,
|
||||||
publicSigningKey!!,
|
publicSigningKey!!,
|
||||||
@ -718,7 +748,8 @@ class Plaintext private constructor(
|
|||||||
nonceTrialsPerByte,
|
nonceTrialsPerByte,
|
||||||
extraBytes,
|
extraBytes,
|
||||||
behaviorBitfield
|
behaviorBitfield
|
||||||
))
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (to == null && type != Type.BROADCAST && destinationRipe != null) {
|
if (to == null && type != Type.BROADCAST && destinationRipe != null) {
|
||||||
to = BitmessageAddress(0, 0, destinationRipe!!)
|
to = BitmessageAddress(0, 0, destinationRipe!!)
|
||||||
@ -726,7 +757,7 @@ class Plaintext private constructor(
|
|||||||
if (preventAck) {
|
if (preventAck) {
|
||||||
ackData = null
|
ackData = null
|
||||||
ackMessage = null
|
ackMessage = null
|
||||||
} else if (type == MSG && ackMessage == null && ackData == null) {
|
} else if (type == MSG && ackMessage == null && ackData == null && to?.has(Feature.DOES_ACK) == true) {
|
||||||
ackData = cryptography().randomBytes(Msg.ACK_LENGTH)
|
ackData = cryptography().randomBytes(Msg.ACK_LENGTH)
|
||||||
}
|
}
|
||||||
if (ttl <= 0) {
|
if (ttl <= 0) {
|
||||||
@ -735,6 +766,12 @@ class Plaintext private constructor(
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmSynthetic
|
||||||
|
inline fun build(block: Builder.() -> Unit): Plaintext {
|
||||||
|
block(this)
|
||||||
|
return build()
|
||||||
|
}
|
||||||
|
|
||||||
fun build(): Plaintext {
|
fun build(): Plaintext {
|
||||||
return Plaintext(this)
|
return Plaintext(this)
|
||||||
}
|
}
|
||||||
@ -742,27 +779,54 @@ class Plaintext private constructor(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
@JvmStatic fun read(type: Type, `in`: InputStream): Plaintext {
|
@JvmStatic
|
||||||
return readWithoutSignature(type, `in`)
|
fun read(type: Type, input: InputStream): Plaintext {
|
||||||
.signature(Decode.varBytes(`in`))
|
return readWithoutSignature(type, input)
|
||||||
|
.signature(Decode.varBytes(input))
|
||||||
.received(UnixTime.now)
|
.received(UnixTime.now)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic fun readWithoutSignature(type: Type, `in`: InputStream): Plaintext.Builder {
|
@JvmStatic
|
||||||
val version = Decode.varInt(`in`)
|
fun readWithoutSignature(type: Type, input: InputStream): Plaintext.Builder {
|
||||||
|
val version = Decode.varInt(input)
|
||||||
return Builder(type)
|
return Builder(type)
|
||||||
.addressVersion(version)
|
.addressVersion(version)
|
||||||
.stream(Decode.varInt(`in`))
|
.stream(Decode.varInt(input))
|
||||||
.behaviorBitfield(Decode.int32(`in`))
|
.behaviorBitfield(Decode.int32(input))
|
||||||
.publicSigningKey(Decode.bytes(`in`, 64))
|
.publicSigningKey(Decode.bytes(input, 64))
|
||||||
.publicEncryptionKey(Decode.bytes(`in`, 64))
|
.publicEncryptionKey(Decode.bytes(input, 64))
|
||||||
.nonceTrialsPerByte(if (version >= 3) Decode.varInt(`in`) else 0)
|
.nonceTrialsPerByte(if (version >= 3) Decode.varInt(input) else 0)
|
||||||
.extraBytes(if (version >= 3) Decode.varInt(`in`) else 0)
|
.extraBytes(if (version >= 3) Decode.varInt(input) else 0)
|
||||||
.destinationRipe(if (type == MSG) Decode.bytes(`in`, 20) else null)
|
.destinationRipe(if (type == MSG) Decode.bytes(input, 20).let {
|
||||||
.encoding(Decode.varInt(`in`))
|
if (it.any { x -> x != 0.toByte() }) it else null
|
||||||
.message(Decode.varBytes(`in`))
|
} else null)
|
||||||
.ackMessage(if (type == MSG) Decode.varBytes(`in`) else null)
|
.encoding(Decode.varInt(input))
|
||||||
|
.message(Decode.varBytes(input))
|
||||||
|
.ackMessage(if (type == MSG) Decode.varBytes(input) else null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmSynthetic
|
||||||
|
inline fun build(type: Type, block: Builder.() -> Unit): Plaintext {
|
||||||
|
val builder = Builder(type)
|
||||||
|
block(builder)
|
||||||
|
return builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class Conversation(val id: UUID, val subject: String, val messages: List<Plaintext>) : Serializable {
|
||||||
|
val participants = messages
|
||||||
|
.map { it.from }
|
||||||
|
.filter { it.privateKey == null || it.isChan }
|
||||||
|
.distinct()
|
||||||
|
|
||||||
|
val extract: String by lazy {
|
||||||
|
messages.firstOrNull { m -> m.labels.any { l -> l.type==Label.Type.UNREAD } }?.text
|
||||||
|
?: messages.lastOrNull()?.text
|
||||||
|
?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasUnread() = messages.any { it.isUnread() }
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,27 @@ import java.nio.ByteBuffer
|
|||||||
* An object that can be written to an [OutputStream]
|
* An object that can be written to an [OutputStream]
|
||||||
*/
|
*/
|
||||||
interface Streamable : Serializable {
|
interface Streamable : Serializable {
|
||||||
fun write(out: OutputStream)
|
fun writer(): StreamableWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SignedStreamable : Streamable {
|
||||||
|
override fun writer(): SignedStreamableWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EncryptedStreamable : SignedStreamable {
|
||||||
|
override fun writer(): EncryptedStreamableWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StreamableWriter: Serializable {
|
||||||
|
fun write(out: OutputStream)
|
||||||
fun write(buffer: ByteBuffer)
|
fun write(buffer: ByteBuffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SignedStreamableWriter : StreamableWriter {
|
||||||
|
fun writeBytesToSign(out: OutputStream)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EncryptedStreamableWriter : SignedStreamableWriter {
|
||||||
|
fun writeUnencrypted(out: OutputStream)
|
||||||
|
fun writeUnencrypted(buffer: ByteBuffer)
|
||||||
|
}
|
||||||
|
@ -26,12 +26,12 @@ class VerAck : MessagePayload {
|
|||||||
|
|
||||||
override val command: MessagePayload.Command = MessagePayload.Command.VERACK
|
override val command: MessagePayload.Command = MessagePayload.Command.VERACK
|
||||||
|
|
||||||
override fun write(out: OutputStream) {
|
|
||||||
// 'verack' doesn't have any payload, so there is nothing to write
|
// 'verack' doesn't have any payload, so there is nothing to write
|
||||||
}
|
override fun writer(): StreamableWriter = EmptyWriter
|
||||||
|
|
||||||
override fun write(buffer: ByteBuffer) {
|
internal object EmptyWriter : StreamableWriter {
|
||||||
// 'verack' doesn't have any payload, so there is nothing to write
|
override fun write(out: OutputStream) = Unit
|
||||||
|
override fun write(buffer: ByteBuffer) = Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -71,32 +71,36 @@ class Version constructor(
|
|||||||
val streams: LongArray = longArrayOf(1)
|
val streams: LongArray = longArrayOf(1)
|
||||||
) : MessagePayload {
|
) : MessagePayload {
|
||||||
|
|
||||||
fun provides(service: Service?): Boolean {
|
fun provides(service: Service?) = service?.isEnabled(services) == true
|
||||||
return service != null && service.isEnabled(services)
|
|
||||||
}
|
|
||||||
|
|
||||||
override val command: MessagePayload.Command = MessagePayload.Command.VERSION
|
override val command: MessagePayload.Command = MessagePayload.Command.VERSION
|
||||||
|
|
||||||
|
override fun writer(): StreamableWriter = Writer(this)
|
||||||
|
|
||||||
|
private class Writer(
|
||||||
|
private val item: Version
|
||||||
|
) : StreamableWriter {
|
||||||
override fun write(out: OutputStream) {
|
override fun write(out: OutputStream) {
|
||||||
Encode.int32(version, out)
|
Encode.int32(item.version, out)
|
||||||
Encode.int64(services, out)
|
Encode.int64(item.services, out)
|
||||||
Encode.int64(timestamp, out)
|
Encode.int64(item.timestamp, out)
|
||||||
addrRecv.write(out, true)
|
item.addrRecv.writer(true).write(out)
|
||||||
addrFrom.write(out, true)
|
item.addrFrom.writer(true).write(out)
|
||||||
Encode.int64(nonce, out)
|
Encode.int64(item.nonce, out)
|
||||||
Encode.varString(userAgent, out)
|
Encode.varString(item.userAgent, out)
|
||||||
Encode.varIntList(streams, out)
|
Encode.varIntList(item.streams, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun write(buffer: ByteBuffer) {
|
override fun write(buffer: ByteBuffer) {
|
||||||
Encode.int32(version, buffer)
|
Encode.int32(item.version, buffer)
|
||||||
Encode.int64(services, buffer)
|
Encode.int64(item.services, buffer)
|
||||||
Encode.int64(timestamp, buffer)
|
Encode.int64(item.timestamp, buffer)
|
||||||
addrRecv.write(buffer, true)
|
item.addrRecv.writer(true).write(buffer)
|
||||||
addrFrom.write(buffer, true)
|
item.addrFrom.writer(true).write(buffer)
|
||||||
Encode.int64(nonce, buffer)
|
Encode.int64(item.nonce, buffer)
|
||||||
Encode.varString(userAgent, buffer)
|
Encode.varString(item.userAgent, buffer)
|
||||||
Encode.varIntList(streams, buffer)
|
Encode.varIntList(item.streams, buffer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Builder {
|
class Builder {
|
||||||
@ -187,9 +191,7 @@ class Version constructor(
|
|||||||
// TODO: NODE_SSL(2);
|
// TODO: NODE_SSL(2);
|
||||||
NODE_NETWORK(1);
|
NODE_NETWORK(1);
|
||||||
|
|
||||||
fun isEnabled(flag: Long): Boolean {
|
fun isEnabled(flag: Long) = (flag and this.flag) != 0L
|
||||||
return (flag and this.flag) != 0L
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun getServiceFlag(vararg services: Service): Long {
|
fun getServiceFlag(vararg services: Service): Long {
|
||||||
|
@ -29,7 +29,12 @@ import java.util.*
|
|||||||
* Users who are subscribed to the sending address will see the message appear in their inbox.
|
* Users who are subscribed to the sending address will see the message appear in their inbox.
|
||||||
* Broadcasts are version 4 or 5.
|
* Broadcasts are version 4 or 5.
|
||||||
*/
|
*/
|
||||||
abstract class Broadcast protected constructor(version: Long, override val stream: Long, protected var encrypted: CryptoBox?, override var plaintext: Plaintext?) : ObjectPayload(version), Encrypted, PlaintextHolder {
|
abstract class Broadcast protected constructor(
|
||||||
|
version: Long,
|
||||||
|
override val stream: Long,
|
||||||
|
protected var encrypted: CryptoBox?,
|
||||||
|
override var plaintext: Plaintext?
|
||||||
|
) : ObjectPayload(version), Encrypted, PlaintextHolder {
|
||||||
|
|
||||||
override val isSigned: Boolean = true
|
override val isSigned: Boolean = true
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package ch.dissem.bitmessage.entity.payload
|
package ch.dissem.bitmessage.entity.payload
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.Streamable
|
import ch.dissem.bitmessage.entity.Streamable
|
||||||
|
import ch.dissem.bitmessage.entity.StreamableWriter
|
||||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey.Companion.PRIVATE_KEY_SIZE
|
import ch.dissem.bitmessage.entity.valueobject.PrivateKey.Companion.PRIVATE_KEY_SIZE
|
||||||
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
||||||
import ch.dissem.bitmessage.utils.*
|
import ch.dissem.bitmessage.utils.*
|
||||||
@ -108,16 +109,37 @@ class CryptoBox : Streamable {
|
|||||||
|
|
||||||
private fun calculateMac(key_m: ByteArray): ByteArray {
|
private fun calculateMac(key_m: ByteArray): ByteArray {
|
||||||
val macData = ByteArrayOutputStream()
|
val macData = ByteArrayOutputStream()
|
||||||
writeWithoutMAC(macData)
|
writer.writeWithoutMAC(macData)
|
||||||
return cryptography().mac(key_m, macData.toByteArray())
|
return cryptography().mac(key_m, macData.toByteArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun writeWithoutMAC(out: OutputStream) {
|
private val writer = Writer(this)
|
||||||
out.write(initializationVector)
|
override fun writer(): StreamableWriter = writer
|
||||||
Encode.int16(curveType, out)
|
|
||||||
writeCoordinateComponent(out, Points.getX(R))
|
private class Writer(
|
||||||
writeCoordinateComponent(out, Points.getY(R))
|
private val item: CryptoBox
|
||||||
out.write(encrypted)
|
) : StreamableWriter {
|
||||||
|
|
||||||
|
override fun write(out: OutputStream) {
|
||||||
|
writeWithoutMAC(out)
|
||||||
|
out.write(item.mac)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun writeWithoutMAC(out: OutputStream) {
|
||||||
|
out.write(item.initializationVector)
|
||||||
|
Encode.int16(item.curveType, out)
|
||||||
|
writeCoordinateComponent(out, Points.getX(item.R))
|
||||||
|
writeCoordinateComponent(out, Points.getY(item.R))
|
||||||
|
out.write(item.encrypted)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(buffer: ByteBuffer) {
|
||||||
|
buffer.put(item.initializationVector)
|
||||||
|
Encode.int16(item.curveType, buffer)
|
||||||
|
writeCoordinateComponent(buffer, Points.getX(item.R))
|
||||||
|
writeCoordinateComponent(buffer, Points.getY(item.R))
|
||||||
|
buffer.put(item.encrypted)
|
||||||
|
buffer.put(item.mac)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun writeCoordinateComponent(out: OutputStream, x: ByteArray) {
|
private fun writeCoordinateComponent(out: OutputStream, x: ByteArray) {
|
||||||
@ -134,18 +156,6 @@ class CryptoBox : Streamable {
|
|||||||
buffer.put(x, offset, length)
|
buffer.put(x, offset, length)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun write(out: OutputStream) {
|
|
||||||
writeWithoutMAC(out)
|
|
||||||
out.write(mac)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun write(buffer: ByteBuffer) {
|
|
||||||
buffer.put(initializationVector)
|
|
||||||
Encode.int16(curveType, buffer)
|
|
||||||
writeCoordinateComponent(buffer, Points.getX(R))
|
|
||||||
writeCoordinateComponent(buffer, Points.getY(R))
|
|
||||||
buffer.put(encrypted)
|
|
||||||
buffer.put(mac)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Builder {
|
class Builder {
|
||||||
@ -187,15 +197,14 @@ class CryptoBox : Streamable {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun build(): CryptoBox {
|
fun build() = CryptoBox(this)
|
||||||
return CryptoBox(this)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val LOG = LoggerFactory.getLogger(CryptoBox::class.java)
|
private val LOG = LoggerFactory.getLogger(CryptoBox::class.java)
|
||||||
|
|
||||||
@JvmStatic fun read(stream: InputStream, length: Int): CryptoBox {
|
@JvmStatic
|
||||||
|
fun read(stream: InputStream, length: Int): CryptoBox {
|
||||||
val counter = AccessCounter()
|
val counter = AccessCounter()
|
||||||
return Builder()
|
return Builder()
|
||||||
.IV(Decode.bytes(stream, 16, counter))
|
.IV(Decode.bytes(stream, 16, counter))
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package ch.dissem.bitmessage.entity.payload
|
package ch.dissem.bitmessage.entity.payload
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.entity.SignedStreamableWriter
|
||||||
import ch.dissem.bitmessage.utils.Decode
|
import ch.dissem.bitmessage.utils.Decode
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
@ -30,14 +31,6 @@ class GenericPayload(version: Long, override val stream: Long, val data: ByteArr
|
|||||||
|
|
||||||
override val type: ObjectType? = null
|
override val type: ObjectType? = null
|
||||||
|
|
||||||
override fun write(out: OutputStream) {
|
|
||||||
out.write(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun write(buffer: ByteBuffer) {
|
|
||||||
buffer.put(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (other !is GenericPayload) return false
|
if (other !is GenericPayload) return false
|
||||||
@ -52,9 +45,27 @@ class GenericPayload(version: Long, override val stream: Long, val data: ByteArr
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun writer(): SignedStreamableWriter = Writer(this)
|
||||||
|
|
||||||
|
private class Writer(
|
||||||
|
private val item: GenericPayload
|
||||||
|
) : SignedStreamableWriter {
|
||||||
|
|
||||||
|
override fun write(out: OutputStream) {
|
||||||
|
out.write(item.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(buffer: ByteBuffer) {
|
||||||
|
buffer.put(item.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeBytesToSign(out: OutputStream) = Unit // nothing to do
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic fun read(version: Long, stream: Long, `is`: InputStream, length: Int): GenericPayload {
|
@JvmStatic
|
||||||
return GenericPayload(version, stream, Decode.bytes(`is`, length))
|
fun read(version: Long, stream: Long, input: InputStream, length: Int) =
|
||||||
}
|
GenericPayload(version, stream, Decode.bytes(input, length))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package ch.dissem.bitmessage.entity.payload
|
package ch.dissem.bitmessage.entity.payload
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
|
import ch.dissem.bitmessage.entity.SignedStreamableWriter
|
||||||
import ch.dissem.bitmessage.utils.Decode
|
import ch.dissem.bitmessage.utils.Decode
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
@ -47,17 +48,28 @@ class GetPubkey : ObjectPayload {
|
|||||||
this.ripeTag = ripeOrTag
|
this.ripeTag = ripeOrTag
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun writer(): SignedStreamableWriter = Writer(this)
|
||||||
|
|
||||||
|
private class Writer(
|
||||||
|
private val item: GetPubkey
|
||||||
|
) : SignedStreamableWriter {
|
||||||
|
|
||||||
override fun write(out: OutputStream) {
|
override fun write(out: OutputStream) {
|
||||||
out.write(ripeTag)
|
out.write(item.ripeTag)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun write(buffer: ByteBuffer) {
|
override fun write(buffer: ByteBuffer) {
|
||||||
buffer.put(ripeTag)
|
buffer.put(item.ripeTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeBytesToSign(out: OutputStream) = Unit // nothing to sign
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic fun read(`is`: InputStream, stream: Long, length: Int, version: Long): GetPubkey {
|
@JvmStatic
|
||||||
return GetPubkey(version, stream, Decode.bytes(`is`, length))
|
fun read(input: InputStream, stream: Long, length: Int, version: Long): GetPubkey {
|
||||||
|
return GetPubkey(version, stream, Decode.bytes(input, length))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import ch.dissem.bitmessage.entity.Encrypted
|
|||||||
import ch.dissem.bitmessage.entity.Plaintext
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
import ch.dissem.bitmessage.entity.Plaintext.Type.MSG
|
import ch.dissem.bitmessage.entity.Plaintext.Type.MSG
|
||||||
import ch.dissem.bitmessage.entity.PlaintextHolder
|
import ch.dissem.bitmessage.entity.PlaintextHolder
|
||||||
|
import ch.dissem.bitmessage.entity.SignedStreamableWriter
|
||||||
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
@ -51,10 +52,6 @@ class Msg : ObjectPayload, Encrypted, PlaintextHolder {
|
|||||||
|
|
||||||
override val isSigned: Boolean = true
|
override val isSigned: Boolean = true
|
||||||
|
|
||||||
override fun writeBytesToSign(out: OutputStream) {
|
|
||||||
plaintext?.write(out, false) ?: throw IllegalStateException("no plaintext data available")
|
|
||||||
}
|
|
||||||
|
|
||||||
override var signature: ByteArray?
|
override var signature: ByteArray?
|
||||||
get() = plaintext?.signature
|
get() = plaintext?.signature
|
||||||
set(signature) {
|
set(signature) {
|
||||||
@ -73,14 +70,6 @@ class Msg : ObjectPayload, Encrypted, PlaintextHolder {
|
|||||||
override val isDecrypted: Boolean
|
override val isDecrypted: Boolean
|
||||||
get() = plaintext != null
|
get() = plaintext != null
|
||||||
|
|
||||||
override fun write(out: OutputStream) {
|
|
||||||
encrypted?.write(out) ?: throw IllegalStateException("Msg must be signed and encrypted before writing it.")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun write(buffer: ByteBuffer) {
|
|
||||||
encrypted?.write(buffer) ?: throw IllegalStateException("Msg must be signed and encrypted before writing it.")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (other !is Msg) return false
|
if (other !is Msg) return false
|
||||||
@ -89,15 +78,34 @@ class Msg : ObjectPayload, Encrypted, PlaintextHolder {
|
|||||||
return stream == other.stream && (encrypted == other.encrypted || plaintext == other.plaintext)
|
return stream == other.stream && (encrypted == other.encrypted || plaintext == other.plaintext)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode() = stream.toInt()
|
||||||
return stream.toInt()
|
|
||||||
|
override fun writer(): SignedStreamableWriter = Writer(this)
|
||||||
|
|
||||||
|
private class Writer(
|
||||||
|
private val item: Msg
|
||||||
|
) : SignedStreamableWriter {
|
||||||
|
|
||||||
|
val encryptedDataWriter = item.encrypted?.writer()
|
||||||
|
|
||||||
|
override fun write(out: OutputStream) {
|
||||||
|
encryptedDataWriter?.write(out) ?: throw IllegalStateException("Msg must be signed and encrypted before writing it.")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(buffer: ByteBuffer) {
|
||||||
|
encryptedDataWriter?.write(buffer) ?: throw IllegalStateException("Msg must be signed and encrypted before writing it.")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeBytesToSign(out: OutputStream) {
|
||||||
|
item.plaintext?.writer(false)?.write(out) ?: throw IllegalStateException("no plaintext data available")
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val ACK_LENGTH = 32
|
val ACK_LENGTH = 32
|
||||||
|
|
||||||
@JvmStatic fun read(`in`: InputStream, stream: Long, length: Int): Msg {
|
@JvmStatic
|
||||||
return Msg(stream, CryptoBox.read(`in`, length))
|
fun read(input: InputStream, stream: Long, length: Int) = Msg(stream, CryptoBox.read(input, length))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,13 +17,14 @@
|
|||||||
package ch.dissem.bitmessage.entity.payload
|
package ch.dissem.bitmessage.entity.payload
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.ObjectMessage
|
import ch.dissem.bitmessage.entity.ObjectMessage
|
||||||
|
import ch.dissem.bitmessage.entity.SignedStreamable
|
||||||
import ch.dissem.bitmessage.entity.Streamable
|
import ch.dissem.bitmessage.entity.Streamable
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The payload of an 'object' command. This is shared by the network.
|
* The payload of an 'object' command. This is shared by the network.
|
||||||
*/
|
*/
|
||||||
abstract class ObjectPayload protected constructor(val version: Long) : Streamable {
|
abstract class ObjectPayload protected constructor(val version: Long) : SignedStreamable {
|
||||||
|
|
||||||
abstract val type: ObjectType?
|
abstract val type: ObjectType?
|
||||||
|
|
||||||
@ -31,10 +32,6 @@ abstract class ObjectPayload protected constructor(val version: Long) : Streamab
|
|||||||
|
|
||||||
open val isSigned: Boolean = false
|
open val isSigned: Boolean = false
|
||||||
|
|
||||||
open fun writeBytesToSign(out: OutputStream) {
|
|
||||||
// nothing to do
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the ECDSA signature which, as of protocol v3, covers the object header starting with the time,
|
* @return the ECDSA signature which, as of protocol v3, covers the object header starting with the time,
|
||||||
* * appended with the data described in this table down to the extra_bytes. Therefore, this must
|
* * appended with the data described in this table down to the extra_bytes. Therefore, this must
|
||||||
|
@ -18,6 +18,8 @@ package ch.dissem.bitmessage.entity.payload
|
|||||||
|
|
||||||
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES
|
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES
|
||||||
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE
|
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE
|
||||||
|
import ch.dissem.bitmessage.entity.EncryptedStreamableWriter
|
||||||
|
import ch.dissem.bitmessage.entity.SignedStreamableWriter
|
||||||
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
@ -42,13 +44,7 @@ abstract class Pubkey protected constructor(version: Long) : ObjectPayload(versi
|
|||||||
|
|
||||||
open val extraBytes: Long = NETWORK_EXTRA_BYTES
|
open val extraBytes: Long = NETWORK_EXTRA_BYTES
|
||||||
|
|
||||||
open fun writeUnencrypted(out: OutputStream) {
|
abstract override fun writer(): EncryptedStreamableWriter
|
||||||
write(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun writeUnencrypted(buffer: ByteBuffer) {
|
|
||||||
write(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bits 0 through 29 are yet undefined
|
* Bits 0 through 29 are yet undefined
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package ch.dissem.bitmessage.entity.payload
|
package ch.dissem.bitmessage.entity.payload
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.entity.EncryptedStreamableWriter
|
||||||
import ch.dissem.bitmessage.utils.Decode
|
import ch.dissem.bitmessage.utils.Decode
|
||||||
import ch.dissem.bitmessage.utils.Encode
|
import ch.dissem.bitmessage.utils.Encode
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
@ -25,21 +26,47 @@ import java.nio.ByteBuffer
|
|||||||
/**
|
/**
|
||||||
* A version 2 public key.
|
* A version 2 public key.
|
||||||
*/
|
*/
|
||||||
open class V2Pubkey constructor(version: Long, override val stream: Long, override val behaviorBitfield: Int, signingKey: ByteArray, encryptionKey: ByteArray) : Pubkey(version) {
|
open class V2Pubkey constructor(
|
||||||
|
version: Long,
|
||||||
|
override val stream: Long,
|
||||||
|
override val behaviorBitfield: Int,
|
||||||
|
signingKey: ByteArray,
|
||||||
|
encryptionKey: ByteArray
|
||||||
|
) : Pubkey(version) {
|
||||||
|
|
||||||
override val signingKey: ByteArray = if (signingKey.size == 64) add0x04(signingKey) else signingKey
|
override val signingKey: ByteArray = if (signingKey.size == 64) add0x04(signingKey) else signingKey
|
||||||
override val encryptionKey: ByteArray = if (encryptionKey.size == 64) add0x04(encryptionKey) else encryptionKey
|
override val encryptionKey: ByteArray = if (encryptionKey.size == 64) add0x04(encryptionKey) else encryptionKey
|
||||||
|
|
||||||
|
override fun writer(): EncryptedStreamableWriter = Writer(this)
|
||||||
|
|
||||||
|
protected open class Writer(
|
||||||
|
private val item: V2Pubkey
|
||||||
|
) : EncryptedStreamableWriter {
|
||||||
|
|
||||||
override fun write(out: OutputStream) {
|
override fun write(out: OutputStream) {
|
||||||
Encode.int32(behaviorBitfield, out)
|
Encode.int32(item.behaviorBitfield, out)
|
||||||
out.write(signingKey, 1, 64)
|
out.write(item.signingKey, 1, 64)
|
||||||
out.write(encryptionKey, 1, 64)
|
out.write(item.encryptionKey, 1, 64)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun write(buffer: ByteBuffer) {
|
override fun write(buffer: ByteBuffer) {
|
||||||
Encode.int32(behaviorBitfield, buffer)
|
Encode.int32(item.behaviorBitfield, buffer)
|
||||||
buffer.put(signingKey, 1, 64)
|
buffer.put(item.signingKey, 1, 64)
|
||||||
buffer.put(encryptionKey, 1, 64)
|
buffer.put(item.encryptionKey, 1, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeBytesToSign(out: OutputStream) {
|
||||||
|
// Nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeUnencrypted(out: OutputStream) {
|
||||||
|
write(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeUnencrypted(buffer: ByteBuffer) {
|
||||||
|
write(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Builder {
|
class Builder {
|
||||||
@ -80,13 +107,13 @@ open class V2Pubkey constructor(version: Long, override val stream: Long, overri
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic fun read(`in`: InputStream, stream: Long): V2Pubkey {
|
@JvmStatic fun read(input: InputStream, stream: Long): V2Pubkey {
|
||||||
return V2Pubkey(
|
return V2Pubkey(
|
||||||
version = 2,
|
version = 2,
|
||||||
stream = stream,
|
stream = stream,
|
||||||
behaviorBitfield = Decode.uint32(`in`).toInt(),
|
behaviorBitfield = Decode.uint32(input).toInt(),
|
||||||
signingKey = Decode.bytes(`in`, 64),
|
signingKey = Decode.bytes(input, 64),
|
||||||
encryptionKey = Decode.bytes(`in`, 64)
|
encryptionKey = Decode.bytes(input, 64)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package ch.dissem.bitmessage.entity.payload
|
package ch.dissem.bitmessage.entity.payload
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.entity.EncryptedStreamableWriter
|
||||||
import ch.dissem.bitmessage.utils.Decode
|
import ch.dissem.bitmessage.utils.Decode
|
||||||
import ch.dissem.bitmessage.utils.Encode
|
import ch.dissem.bitmessage.utils.Encode
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
@ -34,32 +35,8 @@ class V3Pubkey protected constructor(
|
|||||||
override var signature: ByteArray? = null
|
override var signature: ByteArray? = null
|
||||||
) : V2Pubkey(version, stream, behaviorBitfield, signingKey, encryptionKey) {
|
) : V2Pubkey(version, stream, behaviorBitfield, signingKey, encryptionKey) {
|
||||||
|
|
||||||
override fun write(out: OutputStream) {
|
|
||||||
writeBytesToSign(out)
|
|
||||||
Encode.varBytes(
|
|
||||||
signature ?: throw IllegalStateException("signature not available"),
|
|
||||||
out
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun write(buffer: ByteBuffer) {
|
|
||||||
super.write(buffer)
|
|
||||||
Encode.varInt(nonceTrialsPerByte, buffer)
|
|
||||||
Encode.varInt(extraBytes, buffer)
|
|
||||||
Encode.varBytes(
|
|
||||||
signature ?: throw IllegalStateException("signature not available"),
|
|
||||||
buffer
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override val isSigned: Boolean = true
|
override val isSigned: Boolean = true
|
||||||
|
|
||||||
override fun writeBytesToSign(out: OutputStream) {
|
|
||||||
super.write(out)
|
|
||||||
Encode.varInt(nonceTrialsPerByte, out)
|
|
||||||
Encode.varInt(extraBytes, out)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (other !is V3Pubkey) return false
|
if (other !is V3Pubkey) return false
|
||||||
@ -75,6 +52,37 @@ class V3Pubkey protected constructor(
|
|||||||
return Objects.hash(nonceTrialsPerByte, extraBytes)
|
return Objects.hash(nonceTrialsPerByte, extraBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun writer(): EncryptedStreamableWriter = Writer(this)
|
||||||
|
|
||||||
|
protected open class Writer(
|
||||||
|
private val item: V3Pubkey
|
||||||
|
) : V2Pubkey.Writer(item) {
|
||||||
|
|
||||||
|
override fun write(out: OutputStream) {
|
||||||
|
writeBytesToSign(out)
|
||||||
|
Encode.varBytes(
|
||||||
|
item.signature ?: throw IllegalStateException("signature not available"),
|
||||||
|
out
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(buffer: ByteBuffer) {
|
||||||
|
super.write(buffer)
|
||||||
|
Encode.varInt(item.nonceTrialsPerByte, buffer)
|
||||||
|
Encode.varInt(item.extraBytes, buffer)
|
||||||
|
Encode.varBytes(
|
||||||
|
item.signature ?: throw IllegalStateException("signature not available"),
|
||||||
|
buffer
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeBytesToSign(out: OutputStream) {
|
||||||
|
super.write(out)
|
||||||
|
Encode.varInt(item.nonceTrialsPerByte, out)
|
||||||
|
Encode.varInt(item.extraBytes, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Builder {
|
class Builder {
|
||||||
private var streamNumber: Long = 0
|
private var streamNumber: Long = 0
|
||||||
private var behaviorBitfield: Int = 0
|
private var behaviorBitfield: Int = 0
|
||||||
@ -134,16 +142,16 @@ class V3Pubkey protected constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic fun read(`is`: InputStream, stream: Long): V3Pubkey {
|
@JvmStatic fun read(input: InputStream, stream: Long): V3Pubkey {
|
||||||
return V3Pubkey(
|
return V3Pubkey(
|
||||||
version = 3,
|
version = 3,
|
||||||
stream = stream,
|
stream = stream,
|
||||||
behaviorBitfield = Decode.int32(`is`),
|
behaviorBitfield = Decode.int32(input),
|
||||||
signingKey = Decode.bytes(`is`, 64),
|
signingKey = Decode.bytes(input, 64),
|
||||||
encryptionKey = Decode.bytes(`is`, 64),
|
encryptionKey = Decode.bytes(input, 64),
|
||||||
nonceTrialsPerByte = Decode.varInt(`is`),
|
nonceTrialsPerByte = Decode.varInt(input),
|
||||||
extraBytes = Decode.varInt(`is`),
|
extraBytes = Decode.varInt(input),
|
||||||
signature = Decode.varBytes(`is`)
|
signature = Decode.varBytes(input)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ package ch.dissem.bitmessage.entity.payload
|
|||||||
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
import ch.dissem.bitmessage.entity.Plaintext
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
|
import ch.dissem.bitmessage.entity.SignedStreamableWriter
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
@ -38,22 +38,28 @@ open class V4Broadcast : Broadcast {
|
|||||||
throw IllegalArgumentException("Address version 3 or older expected, but was " + senderAddress.version)
|
throw IllegalArgumentException("Address version 3 or older expected, but was " + senderAddress.version)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun writer(): SignedStreamableWriter = Writer(this)
|
||||||
|
|
||||||
|
protected open class Writer(
|
||||||
|
private val item: V4Broadcast
|
||||||
|
) : SignedStreamableWriter {
|
||||||
|
|
||||||
override fun writeBytesToSign(out: OutputStream) {
|
override fun writeBytesToSign(out: OutputStream) {
|
||||||
plaintext?.write(out, false) ?: throw IllegalStateException("no plaintext data available")
|
item.plaintext?.writer(false)?.write(out) ?: throw IllegalStateException("no plaintext data available")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun write(out: OutputStream) {
|
override fun write(out: OutputStream) {
|
||||||
encrypted?.write(out) ?: throw IllegalStateException("broadcast not encrypted")
|
item.encrypted?.writer()?.write(out) ?: throw IllegalStateException("broadcast not encrypted")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun write(buffer: ByteBuffer) {
|
override fun write(buffer: ByteBuffer) {
|
||||||
encrypted?.write(buffer) ?: throw IllegalStateException("broadcast not encrypted")
|
item.encrypted?.writer()?.write(buffer) ?: throw IllegalStateException("broadcast not encrypted")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic fun read(`in`: InputStream, stream: Long, length: Int): V4Broadcast {
|
@JvmStatic
|
||||||
return V4Broadcast(4, stream, CryptoBox.read(`in`, length), null)
|
fun read(input: InputStream, stream: Long, length: Int) =
|
||||||
}
|
V4Broadcast(4, stream, CryptoBox.read(input, length), null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ package ch.dissem.bitmessage.entity.payload
|
|||||||
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
import ch.dissem.bitmessage.entity.Encrypted
|
import ch.dissem.bitmessage.entity.Encrypted
|
||||||
|
import ch.dissem.bitmessage.entity.EncryptedStreamableWriter
|
||||||
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
||||||
import ch.dissem.bitmessage.utils.Decode
|
import ch.dissem.bitmessage.utils.Decode
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
@ -63,29 +64,6 @@ class V4Pubkey : Pubkey, Encrypted {
|
|||||||
override val isDecrypted: Boolean
|
override val isDecrypted: Boolean
|
||||||
get() = decrypted != null
|
get() = decrypted != null
|
||||||
|
|
||||||
override fun write(out: OutputStream) {
|
|
||||||
out.write(tag)
|
|
||||||
encrypted?.write(out) ?: throw IllegalStateException("pubkey is encrypted")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun write(buffer: ByteBuffer) {
|
|
||||||
buffer.put(tag)
|
|
||||||
encrypted?.write(buffer) ?: throw IllegalStateException("pubkey is encrypted")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun writeUnencrypted(out: OutputStream) {
|
|
||||||
decrypted?.write(out) ?: throw IllegalStateException("pubkey is encrypted")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun writeUnencrypted(buffer: ByteBuffer) {
|
|
||||||
decrypted?.write(buffer) ?: throw IllegalStateException("pubkey is encrypted")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun writeBytesToSign(out: OutputStream) {
|
|
||||||
out.write(tag)
|
|
||||||
decrypted?.writeBytesToSign(out) ?: throw IllegalStateException("pubkey is encrypted")
|
|
||||||
}
|
|
||||||
|
|
||||||
override val signingKey: ByteArray
|
override val signingKey: ByteArray
|
||||||
get() = decrypted?.signingKey ?: throw IllegalStateException("pubkey is encrypted")
|
get() = decrypted?.signingKey ?: throw IllegalStateException("pubkey is encrypted")
|
||||||
|
|
||||||
@ -126,14 +104,45 @@ class V4Pubkey : Pubkey, Encrypted {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun writer(): EncryptedStreamableWriter = Writer(this)
|
||||||
|
|
||||||
|
private class Writer(
|
||||||
|
val item: V4Pubkey
|
||||||
|
) : EncryptedStreamableWriter {
|
||||||
|
|
||||||
|
override fun write(out: OutputStream) {
|
||||||
|
out.write(item.tag)
|
||||||
|
item.encrypted?.writer()?.write(out) ?: throw IllegalStateException("pubkey is encrypted")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(buffer: ByteBuffer) {
|
||||||
|
buffer.put(item.tag)
|
||||||
|
item.encrypted?.writer()?.write(buffer) ?: throw IllegalStateException("pubkey is encrypted")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeUnencrypted(out: OutputStream) {
|
||||||
|
item.decrypted?.writer()?.write(out) ?: throw IllegalStateException("pubkey is encrypted")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeUnencrypted(buffer: ByteBuffer) {
|
||||||
|
item.decrypted?.writer()?.write(buffer) ?: throw IllegalStateException("pubkey is encrypted")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeBytesToSign(out: OutputStream) {
|
||||||
|
out.write(item.tag)
|
||||||
|
item.decrypted?.writer()?.writeBytesToSign(out) ?: throw IllegalStateException("pubkey is encrypted")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic fun read(`in`: InputStream, stream: Long, length: Int, encrypted: Boolean): V4Pubkey {
|
@JvmStatic
|
||||||
if (encrypted)
|
fun read(input: InputStream, stream: Long, length: Int, encrypted: Boolean) = if (encrypted) {
|
||||||
return V4Pubkey(stream,
|
V4Pubkey(stream,
|
||||||
Decode.bytes(`in`, 32),
|
Decode.bytes(input, 32),
|
||||||
CryptoBox.read(`in`, length - 32))
|
CryptoBox.read(input, length - 32))
|
||||||
else
|
} else {
|
||||||
return V4Pubkey(V3Pubkey.read(`in`, stream))
|
V4Pubkey(V3Pubkey.read(input, stream))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ package ch.dissem.bitmessage.entity.payload
|
|||||||
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
import ch.dissem.bitmessage.entity.Plaintext
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
|
import ch.dissem.bitmessage.entity.SignedStreamableWriter
|
||||||
import ch.dissem.bitmessage.utils.Decode
|
import ch.dissem.bitmessage.utils.Decode
|
||||||
|
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
@ -40,19 +41,27 @@ class V5Broadcast : V4Broadcast {
|
|||||||
this.tag = senderAddress.tag ?: throw IllegalStateException("version 4 address without tag")
|
this.tag = senderAddress.tag ?: throw IllegalStateException("version 4 address without tag")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun writer(): SignedStreamableWriter = Writer(this)
|
||||||
|
|
||||||
|
private class Writer(
|
||||||
|
private val item: V5Broadcast
|
||||||
|
) : V4Broadcast.Writer(item) {
|
||||||
|
|
||||||
override fun writeBytesToSign(out: OutputStream) {
|
override fun writeBytesToSign(out: OutputStream) {
|
||||||
out.write(tag)
|
out.write(item.tag)
|
||||||
super.writeBytesToSign(out)
|
super.writeBytesToSign(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun write(out: OutputStream) {
|
override fun write(out: OutputStream) {
|
||||||
out.write(tag)
|
out.write(item.tag)
|
||||||
super.write(out)
|
super.write(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic fun read(`is`: InputStream, stream: Long, length: Int): V5Broadcast {
|
@JvmStatic
|
||||||
return V5Broadcast(stream, Decode.bytes(`is`, 32), CryptoBox.read(`is`, length - 32))
|
fun read(input: InputStream, stream: Long, length: Int) =
|
||||||
}
|
V5Broadcast(stream, Decode.bytes(input, 32), CryptoBox.read(input, length - 32))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package ch.dissem.bitmessage.entity.valueobject
|
package ch.dissem.bitmessage.entity.valueobject
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.Streamable
|
import ch.dissem.bitmessage.entity.Streamable
|
||||||
|
import ch.dissem.bitmessage.entity.StreamableWriter
|
||||||
import ch.dissem.bitmessage.utils.Strings
|
import ch.dissem.bitmessage.utils.Strings
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
@ -39,20 +40,29 @@ data class InventoryVector constructor(
|
|||||||
return Arrays.hashCode(hash)
|
return Arrays.hashCode(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun write(out: OutputStream) {
|
|
||||||
out.write(hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun write(buffer: ByteBuffer) {
|
|
||||||
buffer.put(hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return Strings.hex(hash)
|
return Strings.hex(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun writer(): StreamableWriter = Writer(this)
|
||||||
|
|
||||||
|
private class Writer(
|
||||||
|
private val item: InventoryVector
|
||||||
|
) : StreamableWriter {
|
||||||
|
|
||||||
|
override fun write(out: OutputStream) {
|
||||||
|
out.write(item.hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(buffer: ByteBuffer) {
|
||||||
|
buffer.put(item.hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic fun fromHash(hash: ByteArray?): InventoryVector? {
|
@JvmStatic
|
||||||
|
fun fromHash(hash: ByteArray?): InventoryVector? {
|
||||||
return InventoryVector(
|
return InventoryVector(
|
||||||
hash ?: return null
|
hash ?: return null
|
||||||
)
|
)
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package ch.dissem.bitmessage.entity.valueobject
|
package ch.dissem.bitmessage.entity.valueobject
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.Streamable
|
import ch.dissem.bitmessage.entity.Streamable
|
||||||
|
import ch.dissem.bitmessage.entity.StreamableWriter
|
||||||
import ch.dissem.bitmessage.entity.Version
|
import ch.dissem.bitmessage.entity.Version
|
||||||
import ch.dissem.bitmessage.utils.Encode
|
import ch.dissem.bitmessage.utils.Encode
|
||||||
import ch.dissem.bitmessage.utils.UnixTime
|
import ch.dissem.bitmessage.utils.UnixTime
|
||||||
@ -77,9 +78,7 @@ data class NetworkAddress(
|
|||||||
|
|
||||||
fun provides(service: Version.Service?): Boolean = service?.isEnabled(services) ?: false
|
fun provides(service: Version.Service?): Boolean = service?.isEnabled(services) ?: false
|
||||||
|
|
||||||
fun toInetAddress(): InetAddress {
|
fun toInetAddress() = InetAddress.getByAddress(IPv6)
|
||||||
return InetAddress.getByAddress(IPv6)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
@ -98,32 +97,40 @@ data class NetworkAddress(
|
|||||||
return "[" + toInetAddress() + "]:" + port
|
return "[" + toInetAddress() + "]:" + port
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun write(out: OutputStream) {
|
fun writer(light: Boolean): StreamableWriter = Writer(
|
||||||
write(out, false)
|
item = this,
|
||||||
}
|
light = light
|
||||||
|
)
|
||||||
|
|
||||||
fun write(out: OutputStream, light: Boolean) {
|
override fun writer(): StreamableWriter = Writer(
|
||||||
|
item = this
|
||||||
|
)
|
||||||
|
|
||||||
|
private class Writer(
|
||||||
|
private val item: NetworkAddress,
|
||||||
|
private val light: Boolean = false
|
||||||
|
) : StreamableWriter {
|
||||||
|
|
||||||
|
override fun write(out: OutputStream) {
|
||||||
if (!light) {
|
if (!light) {
|
||||||
Encode.int64(time, out)
|
Encode.int64(item.time, out)
|
||||||
Encode.int32(stream, out)
|
Encode.int32(item.stream, out)
|
||||||
}
|
}
|
||||||
Encode.int64(services, out)
|
Encode.int64(item.services, out)
|
||||||
out.write(IPv6)
|
out.write(item.IPv6)
|
||||||
Encode.int16(port, out)
|
Encode.int16(item.port, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun write(buffer: ByteBuffer) {
|
override fun write(buffer: ByteBuffer) {
|
||||||
write(buffer, false)
|
if (!light) {
|
||||||
|
Encode.int64(item.time, buffer)
|
||||||
|
Encode.int32(item.stream, buffer)
|
||||||
|
}
|
||||||
|
Encode.int64(item.services, buffer)
|
||||||
|
buffer.put(item.IPv6)
|
||||||
|
Encode.int16(item.port, buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun write(buffer: ByteBuffer, light: Boolean) {
|
|
||||||
if (!light) {
|
|
||||||
Encode.int64(time, buffer)
|
|
||||||
Encode.int32(stream, buffer)
|
|
||||||
}
|
|
||||||
Encode.int64(services, buffer)
|
|
||||||
buffer.put(IPv6)
|
|
||||||
Encode.int16(port, buffer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Builder {
|
class Builder {
|
||||||
@ -194,6 +201,7 @@ data class NetworkAddress(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmField val ANY = NetworkAddress(time = 0, stream = 0, services = 0, IPv6 = ByteArray(16), port = 0)
|
@JvmField
|
||||||
|
val ANY = NetworkAddress(time = 0, stream = 0, services = 0, IPv6 = ByteArray(16), port = 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ package ch.dissem.bitmessage.entity.valueobject
|
|||||||
import ch.dissem.bitmessage.InternalContext
|
import ch.dissem.bitmessage.InternalContext
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
import ch.dissem.bitmessage.entity.Streamable
|
import ch.dissem.bitmessage.entity.Streamable
|
||||||
|
import ch.dissem.bitmessage.entity.StreamableWriter
|
||||||
import ch.dissem.bitmessage.entity.payload.Pubkey
|
import ch.dissem.bitmessage.entity.payload.Pubkey
|
||||||
import ch.dissem.bitmessage.exception.ApplicationException
|
import ch.dissem.bitmessage.exception.ApplicationException
|
||||||
import ch.dissem.bitmessage.factory.Factory
|
import ch.dissem.bitmessage.factory.Factory
|
||||||
@ -66,7 +67,52 @@ data class PrivateKey(
|
|||||||
builder.nonceTrialsPerByte, builder.extraBytes, *builder.features)
|
builder.nonceTrialsPerByte, builder.extraBytes, *builder.features)
|
||||||
)
|
)
|
||||||
|
|
||||||
private class Builder internal constructor(internal val version: Long, internal val stream: Long, internal val shorter: Boolean) {
|
override fun equals(other: Any?) = other is PrivateKey
|
||||||
|
&& Arrays.equals(privateEncryptionKey, other.privateEncryptionKey)
|
||||||
|
&& Arrays.equals(privateSigningKey, other.privateSigningKey)
|
||||||
|
&& pubkey == other.pubkey
|
||||||
|
|
||||||
|
override fun hashCode() = pubkey.hashCode()
|
||||||
|
|
||||||
|
override fun writer(): StreamableWriter = Writer(this)
|
||||||
|
|
||||||
|
private class Writer(
|
||||||
|
private val item: PrivateKey
|
||||||
|
) : StreamableWriter {
|
||||||
|
|
||||||
|
override fun write(out: OutputStream) {
|
||||||
|
Encode.varInt(item.pubkey.version, out)
|
||||||
|
Encode.varInt(item.pubkey.stream, out)
|
||||||
|
val baos = ByteArrayOutputStream()
|
||||||
|
item.pubkey.writer().writeUnencrypted(baos)
|
||||||
|
Encode.varInt(baos.size(), out)
|
||||||
|
out.write(baos.toByteArray())
|
||||||
|
Encode.varBytes(item.privateSigningKey, out)
|
||||||
|
Encode.varBytes(item.privateEncryptionKey, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(buffer: ByteBuffer) {
|
||||||
|
Encode.varInt(item.pubkey.version, buffer)
|
||||||
|
Encode.varInt(item.pubkey.stream, buffer)
|
||||||
|
try {
|
||||||
|
val baos = ByteArrayOutputStream()
|
||||||
|
item.pubkey.writer().writeUnencrypted(baos)
|
||||||
|
Encode.varBytes(baos.toByteArray(), buffer)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
throw ApplicationException(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
Encode.varBytes(item.privateSigningKey, buffer)
|
||||||
|
Encode.varBytes(item.privateEncryptionKey, buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Builder internal constructor(
|
||||||
|
internal val version: Long,
|
||||||
|
internal val stream: Long,
|
||||||
|
internal val shorter: Boolean
|
||||||
|
) {
|
||||||
|
|
||||||
internal var seed: ByteArray? = null
|
internal var seed: ByteArray? = null
|
||||||
internal var nextNonce: Long = 0
|
internal var nextNonce: Long = 0
|
||||||
@ -129,44 +175,12 @@ data class PrivateKey(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun write(out: OutputStream) {
|
|
||||||
Encode.varInt(pubkey.version, out)
|
|
||||||
Encode.varInt(pubkey.stream, out)
|
|
||||||
val baos = ByteArrayOutputStream()
|
|
||||||
pubkey.writeUnencrypted(baos)
|
|
||||||
Encode.varInt(baos.size(), out)
|
|
||||||
out.write(baos.toByteArray())
|
|
||||||
Encode.varBytes(privateSigningKey, out)
|
|
||||||
Encode.varBytes(privateEncryptionKey, out)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override fun write(buffer: ByteBuffer) {
|
|
||||||
Encode.varInt(pubkey.version, buffer)
|
|
||||||
Encode.varInt(pubkey.stream, buffer)
|
|
||||||
try {
|
|
||||||
val baos = ByteArrayOutputStream()
|
|
||||||
pubkey.writeUnencrypted(baos)
|
|
||||||
Encode.varBytes(baos.toByteArray(), buffer)
|
|
||||||
} catch (e: IOException) {
|
|
||||||
throw ApplicationException(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
Encode.varBytes(privateSigningKey, buffer)
|
|
||||||
Encode.varBytes(privateEncryptionKey, buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?) = other is PrivateKey
|
|
||||||
&& Arrays.equals(privateEncryptionKey, other.privateEncryptionKey)
|
|
||||||
&& Arrays.equals(privateSigningKey, other.privateSigningKey)
|
|
||||||
&& pubkey == other.pubkey
|
|
||||||
|
|
||||||
override fun hashCode() = pubkey.hashCode()
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmField val PRIVATE_KEY_SIZE = 32
|
@JvmField
|
||||||
|
val PRIVATE_KEY_SIZE = 32
|
||||||
|
|
||||||
@JvmStatic fun deterministic(passphrase: String, numberOfAddresses: Int, version: Long, stream: Long, shorter: Boolean): List<PrivateKey> {
|
@JvmStatic
|
||||||
|
fun deterministic(passphrase: String, numberOfAddresses: Int, version: Long, stream: Long, shorter: Boolean): List<PrivateKey> {
|
||||||
val result = ArrayList<PrivateKey>(numberOfAddresses)
|
val result = ArrayList<PrivateKey>(numberOfAddresses)
|
||||||
val builder = Builder(version, stream, shorter).seed(passphrase)
|
val builder = Builder(version, stream, shorter).seed(passphrase)
|
||||||
for (i in 0..numberOfAddresses - 1) {
|
for (i in 0..numberOfAddresses - 1) {
|
||||||
@ -176,13 +190,14 @@ data class PrivateKey(
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic fun read(`is`: InputStream): PrivateKey {
|
@JvmStatic
|
||||||
val version = Decode.varInt(`is`).toInt()
|
fun read(input: InputStream): PrivateKey {
|
||||||
val stream = Decode.varInt(`is`)
|
val version = Decode.varInt(input).toInt()
|
||||||
val len = Decode.varInt(`is`).toInt()
|
val stream = Decode.varInt(input)
|
||||||
val pubkey = Factory.readPubkey(version.toLong(), stream, `is`, len, false) ?: throw ApplicationException("Unknown pubkey version encountered")
|
val len = Decode.varInt(input).toInt()
|
||||||
val signingKey = Decode.varBytes(`is`)
|
val pubkey = Factory.readPubkey(version.toLong(), stream, input, len, false) ?: throw ApplicationException("Unknown pubkey version encountered")
|
||||||
val encryptionKey = Decode.varBytes(`is`)
|
val signingKey = Decode.varBytes(input)
|
||||||
|
val encryptionKey = Decode.varBytes(input)
|
||||||
return PrivateKey(signingKey, encryptionKey, pubkey)
|
return PrivateKey(signingKey, encryptionKey, pubkey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,33 +37,33 @@ import java.util.*
|
|||||||
data class Message constructor(
|
data class Message constructor(
|
||||||
val subject: String,
|
val subject: String,
|
||||||
val body: String,
|
val body: String,
|
||||||
val parents: List<InventoryVector>,
|
val parents: List<InventoryVector> = emptyList(),
|
||||||
val files: List<Attachment>
|
val files: List<Attachment> = emptyList()
|
||||||
) : ExtendedEncoding.ExtendedType {
|
) : ExtendedEncoding.ExtendedType {
|
||||||
|
|
||||||
override val type: String = TYPE
|
override val type: String = TYPE
|
||||||
|
|
||||||
override fun pack(): MPMap<MPString, MPType<*>> {
|
override fun pack(): MPMap<MPString, MPType<*>> {
|
||||||
val result = MPMap<MPString, MPType<*>>()
|
val result = MPMap<MPString, MPType<*>>()
|
||||||
result.put(mp(""), mp(TYPE))
|
result["".mp] = TYPE.mp
|
||||||
result.put(mp("subject"), mp(subject))
|
result["subject".mp] = subject.mp
|
||||||
result.put(mp("body"), mp(body))
|
result["body".mp] = body.mp
|
||||||
|
|
||||||
if (!files.isEmpty()) {
|
if (!files.isEmpty()) {
|
||||||
val items = MPArray<MPMap<MPString, MPType<*>>>()
|
val items = MPArray<MPMap<MPString, MPType<*>>>()
|
||||||
result.put(mp("files"), items)
|
result["files".mp] = items
|
||||||
for (file in files) {
|
for (file in files) {
|
||||||
val item = MPMap<MPString, MPType<*>>()
|
val item = MPMap<MPString, MPType<*>>()
|
||||||
item.put(mp("name"), mp(file.name))
|
item["name".mp] = file.name.mp
|
||||||
item.put(mp("data"), mp(*file.data))
|
item["data".mp] = file.data.mp
|
||||||
item.put(mp("type"), mp(file.type))
|
item["type".mp] = file.type.mp
|
||||||
item.put(mp("disposition"), mp(file.disposition.name))
|
item["disposition".mp] = file.disposition.name.mp
|
||||||
items.add(item)
|
items.add(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!parents.isEmpty()) {
|
if (!parents.isEmpty()) {
|
||||||
val items = MPArray<MPBinary>()
|
val items = MPArray<MPBinary>()
|
||||||
result.put(mp("parents"), items)
|
result["parents".mp] = items
|
||||||
for ((hash) in parents) {
|
for ((hash) in parents) {
|
||||||
items.add(mp(*hash))
|
items.add(mp(*hash))
|
||||||
}
|
}
|
||||||
@ -139,26 +139,26 @@ data class Message constructor(
|
|||||||
override val type: String = TYPE
|
override val type: String = TYPE
|
||||||
|
|
||||||
override fun unpack(map: MPMap<MPString, MPType<*>>): Message {
|
override fun unpack(map: MPMap<MPString, MPType<*>>): Message {
|
||||||
val subject = str(map[mp("subject")]) ?: ""
|
val subject = str(map["subject".mp]) ?: ""
|
||||||
val body = str(map[mp("body")]) ?: ""
|
val body = str(map["body".mp]) ?: ""
|
||||||
val parents = LinkedList<InventoryVector>()
|
val parents = LinkedList<InventoryVector>()
|
||||||
val files = LinkedList<Attachment>()
|
val files = LinkedList<Attachment>()
|
||||||
val mpParents = map[mp("parents")] as? MPArray<*>
|
val mpParents = map["parents".mp] as? MPArray<*>
|
||||||
for (parent in mpParents ?: emptyList<MPArray<MPBinary>>()) {
|
for (parent in mpParents ?: emptyList<MPArray<MPBinary>>()) {
|
||||||
parents.add(InventoryVector.fromHash(
|
parents.add(InventoryVector.fromHash(
|
||||||
(parent as? MPBinary)?.value ?: continue
|
(parent as? MPBinary)?.value ?: continue
|
||||||
) ?: continue)
|
) ?: continue)
|
||||||
}
|
}
|
||||||
val mpFiles = map[mp("files")] as? MPArray<*>
|
val mpFiles = map["files".mp] as? MPArray<*>
|
||||||
for (item in mpFiles ?: emptyList<Any>()) {
|
for (item in mpFiles ?: emptyList<Any>()) {
|
||||||
if (item is MPMap<*, *>) {
|
if (item is MPMap<*, *>) {
|
||||||
val b = Attachment.Builder()
|
val b = Attachment.Builder()
|
||||||
b.name(str(item[mp("name")])!!)
|
b.name(str(item["name".mp])!!)
|
||||||
b.data(
|
b.data(
|
||||||
bin(item[mp("data")] ?: continue) ?: continue
|
bin(item["data".mp] ?: continue) ?: continue
|
||||||
)
|
)
|
||||||
b.type(str(item[mp("type")])!!)
|
b.type(str(item["type".mp])!!)
|
||||||
val disposition = str(item[mp("disposition")])
|
val disposition = str(item["disposition".mp])
|
||||||
if ("inline" == disposition) {
|
if ("inline" == disposition) {
|
||||||
b.inline()
|
b.inline()
|
||||||
} else if ("attachment" == disposition) {
|
} else if ("attachment" == disposition) {
|
||||||
@ -179,6 +179,6 @@ data class Message constructor(
|
|||||||
companion object {
|
companion object {
|
||||||
private val LOG = LoggerFactory.getLogger(Message::class.java)
|
private val LOG = LoggerFactory.getLogger(Message::class.java)
|
||||||
|
|
||||||
val TYPE = "message"
|
const val TYPE = "message"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,9 +35,9 @@ data class Vote constructor(val msgId: InventoryVector, val vote: String) : Exte
|
|||||||
|
|
||||||
override fun pack(): MPMap<MPString, MPType<*>> {
|
override fun pack(): MPMap<MPString, MPType<*>> {
|
||||||
val result = MPMap<MPString, MPType<*>>()
|
val result = MPMap<MPString, MPType<*>>()
|
||||||
result.put(mp(""), mp(TYPE))
|
result.put("".mp, TYPE.mp)
|
||||||
result.put(mp("msgId"), mp(*msgId.hash))
|
result.put("msgId".mp, msgId.hash.mp)
|
||||||
result.put(mp("vote"), mp(vote))
|
result.put("vote".mp, vote.mp)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,13 +77,14 @@ data class Vote constructor(val msgId: InventoryVector, val vote: String) : Exte
|
|||||||
get() = TYPE
|
get() = TYPE
|
||||||
|
|
||||||
override fun unpack(map: MPMap<MPString, MPType<*>>): Vote {
|
override fun unpack(map: MPMap<MPString, MPType<*>>): Vote {
|
||||||
val msgId = InventoryVector.fromHash((map[mp("msgId")] as? MPBinary)?.value) ?: throw IllegalArgumentException("data doesn't contain proper msgId")
|
val msgId = InventoryVector.fromHash((map["msgId".mp] as? MPBinary)?.value) ?: throw IllegalArgumentException("data doesn't contain proper msgId")
|
||||||
val vote = str(map[mp("vote")]) ?: throw IllegalArgumentException("no vote given")
|
val vote = str(map["vote".mp]) ?: throw IllegalArgumentException("no vote given")
|
||||||
return Vote(msgId, vote)
|
return Vote(msgId, vote)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmField val TYPE = "vote"
|
@JvmField
|
||||||
|
val TYPE = "vote"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
@ -54,9 +54,8 @@ object ExtendedEncodingFactory {
|
|||||||
fun unzip(zippedData: ByteArray): ExtendedEncoding? {
|
fun unzip(zippedData: ByteArray): ExtendedEncoding? {
|
||||||
try {
|
try {
|
||||||
InflaterInputStream(ByteArrayInputStream(zippedData)).use { unzipper ->
|
InflaterInputStream(ByteArrayInputStream(zippedData)).use { unzipper ->
|
||||||
val reader = Reader.getInstance()
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
val map = reader.read(unzipper) as MPMap<MPString, MPType<*>>
|
val map = Reader.read(unzipper) as MPMap<MPString, MPType<*>>
|
||||||
val messageType = map[KEY_MESSAGE_TYPE]
|
val messageType = map[KEY_MESSAGE_TYPE]
|
||||||
if (messageType == null) {
|
if (messageType == null) {
|
||||||
LOG.error("Missing message type")
|
LOG.error("Missing message type")
|
||||||
|
@ -156,11 +156,11 @@ object Factory {
|
|||||||
return GetPubkey.read(stream, streamNumber, length, version)
|
return GetPubkey.read(stream, streamNumber, length, version)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic fun readPubkey(version: Long, stream: Long, `is`: InputStream, length: Int, encrypted: Boolean): Pubkey? {
|
@JvmStatic fun readPubkey(version: Long, stream: Long, input: InputStream, length: Int, encrypted: Boolean): Pubkey? {
|
||||||
when (version.toInt()) {
|
when (version.toInt()) {
|
||||||
2 -> return V2Pubkey.read(`is`, stream)
|
2 -> return V2Pubkey.read(input, stream)
|
||||||
3 -> return V3Pubkey.read(`is`, stream)
|
3 -> return V3Pubkey.read(input, stream)
|
||||||
4 -> return V4Pubkey.read(`is`, stream, length, encrypted)
|
4 -> return V4Pubkey.read(input, stream, length, encrypted)
|
||||||
}
|
}
|
||||||
LOG.debug("Unexpected pubkey version $version, handling as generic payload object")
|
LOG.debug("Unexpected pubkey version $version, handling as generic payload object")
|
||||||
return null
|
return null
|
||||||
|
@ -39,130 +39,88 @@ object V3MessageFactory {
|
|||||||
private val LOG = LoggerFactory.getLogger(V3MessageFactory::class.java)
|
private val LOG = LoggerFactory.getLogger(V3MessageFactory::class.java)
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun read(`in`: InputStream): NetworkMessage? {
|
fun read(input: InputStream): NetworkMessage? {
|
||||||
findMagic(`in`)
|
findMagic(input)
|
||||||
val command = getCommand(`in`)
|
val command = getCommand(input)
|
||||||
val length = Decode.uint32(`in`).toInt()
|
val length = Decode.uint32(input).toInt()
|
||||||
if (length > 1600003) {
|
if (length > 1600003) {
|
||||||
throw NodeException("Payload of $length bytes received, no more than 1600003 was expected.")
|
throw NodeException("Payload of $length bytes received, no more than 1600003 was expected.")
|
||||||
}
|
}
|
||||||
val checksum = Decode.bytes(`in`, 4)
|
val checksum = Decode.bytes(input, 4)
|
||||||
|
|
||||||
val payloadBytes = Decode.bytes(`in`, length)
|
val payloadBytes = Decode.bytes(input, length)
|
||||||
|
|
||||||
if (testChecksum(checksum, payloadBytes)) {
|
if (testChecksum(checksum, payloadBytes)) {
|
||||||
val payload = getPayload(command, ByteArrayInputStream(payloadBytes), length)
|
val payload = getPayload(command, ByteArrayInputStream(payloadBytes), length)
|
||||||
if (payload != null)
|
return payload?.let { NetworkMessage(payload) }
|
||||||
return NetworkMessage(payload)
|
|
||||||
else
|
|
||||||
return null
|
|
||||||
} else {
|
} else {
|
||||||
throw IOException("Checksum failed for message '$command'")
|
throw IOException("Checksum failed for message '$command'")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getPayload(command: String, stream: InputStream, length: Int): MessagePayload? {
|
fun getPayload(command: String, stream: InputStream, length: Int): MessagePayload? = when (command) {
|
||||||
when (command) {
|
"version" -> parseVersion(stream)
|
||||||
"version" -> return parseVersion(stream)
|
"verack" -> VerAck()
|
||||||
"verack" -> return VerAck()
|
"addr" -> Addr(parseList(stream) { parseAddress(it, false) })
|
||||||
"addr" -> return parseAddr(stream)
|
"inv" -> Inv(parseList(stream) { parseInventoryVector(it) })
|
||||||
"inv" -> return parseInv(stream)
|
"getdata" -> GetData(parseList(stream) { parseInventoryVector(it) })
|
||||||
"getdata" -> return parseGetData(stream)
|
"object" -> readObject(stream, length)
|
||||||
"object" -> return readObject(stream, length)
|
"custom" -> readCustom(stream, length)
|
||||||
"custom" -> return readCustom(stream, length)
|
|
||||||
else -> {
|
else -> {
|
||||||
LOG.debug("Unknown command: " + command)
|
LOG.debug("Unknown command: $command")
|
||||||
return null
|
null
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun readCustom(`in`: InputStream, length: Int): MessagePayload {
|
private fun readCustom(input: InputStream, length: Int): MessagePayload = CustomMessage.read(input, length)
|
||||||
return CustomMessage.read(`in`, length)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun readObject(`in`: InputStream, length: Int): ObjectMessage {
|
fun readObject(input: InputStream, length: Int): ObjectMessage {
|
||||||
val counter = AccessCounter()
|
val counter = AccessCounter()
|
||||||
val nonce = Decode.bytes(`in`, 8, counter)
|
val nonce = Decode.bytes(input, 8, counter)
|
||||||
val expiresTime = Decode.int64(`in`, counter)
|
val expiresTime = Decode.int64(input, counter)
|
||||||
val objectType = Decode.uint32(`in`, counter)
|
val objectType = Decode.uint32(input, counter)
|
||||||
val version = Decode.varInt(`in`, counter)
|
val version = Decode.varInt(input, counter)
|
||||||
val stream = Decode.varInt(`in`, counter)
|
val stream = Decode.varInt(input, counter)
|
||||||
|
|
||||||
val data = Decode.bytes(`in`, length - counter.length())
|
val data = Decode.bytes(input, length - counter.length())
|
||||||
var payload: ObjectPayload
|
val payload: ObjectPayload = try {
|
||||||
try {
|
Factory.getObjectPayload(objectType, version, stream, ByteArrayInputStream(data), data.size)
|
||||||
val dataStream = ByteArrayInputStream(data)
|
|
||||||
payload = Factory.getObjectPayload(objectType, version, stream, dataStream, data.size)
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
if (LOG.isTraceEnabled) {
|
if (LOG.isTraceEnabled) {
|
||||||
LOG.trace("Could not parse object payload - using generic payload instead", e)
|
LOG.trace("Could not parse object payload - using generic payload instead", e)
|
||||||
LOG.trace(Strings.hex(data))
|
LOG.trace(Strings.hex(data))
|
||||||
}
|
}
|
||||||
payload = GenericPayload(version, stream, data)
|
GenericPayload(version, stream, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ObjectMessage.Builder()
|
return ObjectMessage(
|
||||||
.nonce(nonce)
|
nonce, expiresTime, payload, objectType, version, stream
|
||||||
.expiresTime(expiresTime)
|
)
|
||||||
.objectType(objectType)
|
|
||||||
.stream(stream)
|
|
||||||
.payload(payload)
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseGetData(stream: InputStream): GetData {
|
private fun <T> parseList(stream: InputStream, reader: (InputStream) -> (T)): List<T> {
|
||||||
val count = Decode.varInt(stream)
|
val count = Decode.varInt(stream)
|
||||||
val inventoryVectors = LinkedList<InventoryVector>()
|
val items = LinkedList<T>()
|
||||||
for (i in 0..count - 1) {
|
for (i in 0 until count) {
|
||||||
inventoryVectors.add(parseInventoryVector(stream))
|
items.add(reader(stream))
|
||||||
}
|
}
|
||||||
return GetData(inventoryVectors)
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseInv(stream: InputStream): Inv {
|
private fun parseVersion(stream: InputStream) = Version(
|
||||||
val count = Decode.varInt(stream)
|
version = Decode.int32(stream),
|
||||||
val inventoryVectors = LinkedList<InventoryVector>()
|
services = Decode.int64(stream),
|
||||||
for (i in 0..count - 1) {
|
timestamp = Decode.int64(stream),
|
||||||
inventoryVectors.add(parseInventoryVector(stream))
|
addrRecv = parseAddress(stream, true),
|
||||||
}
|
addrFrom = parseAddress(stream, true),
|
||||||
return Inv(inventoryVectors)
|
nonce = Decode.int64(stream),
|
||||||
}
|
userAgent = Decode.varString(stream),
|
||||||
|
streams = Decode.varIntList(stream)
|
||||||
|
)
|
||||||
|
|
||||||
private fun parseAddr(stream: InputStream): Addr {
|
private fun parseInventoryVector(stream: InputStream) = InventoryVector(Decode.bytes(stream, 32))
|
||||||
val count = Decode.varInt(stream)
|
|
||||||
val networkAddresses = LinkedList<NetworkAddress>()
|
|
||||||
for (i in 0..count - 1) {
|
|
||||||
networkAddresses.add(parseAddress(stream, false))
|
|
||||||
}
|
|
||||||
return Addr(networkAddresses)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun parseVersion(stream: InputStream): Version {
|
|
||||||
val version = Decode.int32(stream)
|
|
||||||
val services = Decode.int64(stream)
|
|
||||||
val timestamp = Decode.int64(stream)
|
|
||||||
val addrRecv = parseAddress(stream, true)
|
|
||||||
val addrFrom = parseAddress(stream, true)
|
|
||||||
val nonce = Decode.int64(stream)
|
|
||||||
val userAgent = Decode.varString(stream)
|
|
||||||
val streamNumbers = Decode.varIntList(stream)
|
|
||||||
|
|
||||||
return Version.Builder()
|
|
||||||
.version(version)
|
|
||||||
.services(services)
|
|
||||||
.timestamp(timestamp)
|
|
||||||
.addrRecv(addrRecv).addrFrom(addrFrom)
|
|
||||||
.nonce(nonce)
|
|
||||||
.userAgent(userAgent)
|
|
||||||
.streams(*streamNumbers).build()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun parseInventoryVector(stream: InputStream): InventoryVector {
|
|
||||||
return InventoryVector(Decode.bytes(stream, 32))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun parseAddress(stream: InputStream, light: Boolean): NetworkAddress {
|
private fun parseAddress(stream: InputStream, light: Boolean): NetworkAddress {
|
||||||
val time: Long
|
val time: Long
|
||||||
@ -177,23 +135,15 @@ object V3MessageFactory {
|
|||||||
val services = Decode.int64(stream)
|
val services = Decode.int64(stream)
|
||||||
val ipv6 = Decode.bytes(stream, 16)
|
val ipv6 = Decode.bytes(stream, 16)
|
||||||
val port = Decode.uint16(stream)
|
val port = Decode.uint16(stream)
|
||||||
return NetworkAddress.Builder()
|
|
||||||
.time(time)
|
return NetworkAddress(
|
||||||
.stream(streamNumber)
|
time, streamNumber, services, ipv6, port
|
||||||
.services(services)
|
)
|
||||||
.ipv6(ipv6)
|
|
||||||
.port(port)
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun testChecksum(checksum: ByteArray, payload: ByteArray): Boolean {
|
private fun testChecksum(checksum: ByteArray, payload: ByteArray): Boolean {
|
||||||
val payloadChecksum = cryptography().sha512(payload)
|
val payloadChecksum = cryptography().sha512(payload)
|
||||||
for (i in checksum.indices) {
|
return checksum.indices.none { checksum[it] != payloadChecksum[it] }
|
||||||
if (checksum[i] != payloadChecksum[i]) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getCommand(stream: InputStream): String {
|
private fun getCommand(stream: InputStream): String {
|
||||||
@ -210,10 +160,10 @@ object V3MessageFactory {
|
|||||||
return String(bytes, 0, end, Charsets.US_ASCII)
|
return String(bytes, 0, end, Charsets.US_ASCII)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findMagic(`in`: InputStream) {
|
private fun findMagic(input: InputStream) {
|
||||||
var pos = 0
|
var pos = 0
|
||||||
for (i in 0..1619999) {
|
for (i in 0..1619999) {
|
||||||
val b = `in`.read().toByte()
|
val b = input.read().toByte()
|
||||||
if (b == NetworkMessage.MAGIC_BYTES[pos]) {
|
if (b == NetworkMessage.MAGIC_BYTES[pos]) {
|
||||||
if (pos + 1 == NetworkMessage.MAGIC_BYTES.size) {
|
if (pos + 1 == NetworkMessage.MAGIC_BYTES.size) {
|
||||||
return
|
return
|
||||||
|
@ -30,8 +30,7 @@ import java.util.*
|
|||||||
* Similar to the [V3MessageFactory], but used for NIO buffers which may or may not contain a whole message.
|
* Similar to the [V3MessageFactory], but used for NIO buffers which may or may not contain a whole message.
|
||||||
*/
|
*/
|
||||||
class V3MessageReader {
|
class V3MessageReader {
|
||||||
private var headerBuffer: ByteBuffer? = null
|
val buffer: ByteBuffer = ByteBuffer.allocate(MAX_PAYLOAD_SIZE)
|
||||||
private var dataBuffer: ByteBuffer? = null
|
|
||||||
|
|
||||||
private var state: ReaderState? = ReaderState.MAGIC
|
private var state: ReaderState? = ReaderState.MAGIC
|
||||||
private var command: String? = null
|
private var command: String? = null
|
||||||
@ -40,89 +39,83 @@ class V3MessageReader {
|
|||||||
|
|
||||||
private val messages = LinkedList<NetworkMessage>()
|
private val messages = LinkedList<NetworkMessage>()
|
||||||
|
|
||||||
fun getActiveBuffer(): ByteBuffer {
|
|
||||||
if (state != null && state != ReaderState.DATA) {
|
|
||||||
if (headerBuffer == null) {
|
|
||||||
headerBuffer = BufferPool.allocateHeaderBuffer()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return if (state == ReaderState.DATA)
|
|
||||||
dataBuffer ?: throw IllegalStateException("data buffer is null")
|
|
||||||
else
|
|
||||||
headerBuffer ?: throw IllegalStateException("header buffer is null")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun update() {
|
fun update() {
|
||||||
if (state != ReaderState.DATA) {
|
if (state != ReaderState.DATA) {
|
||||||
getActiveBuffer() // in order to initialize
|
buffer.flip()
|
||||||
headerBuffer?.flip() ?: throw IllegalStateException("header buffer is null")
|
}
|
||||||
|
var s = when (state) {
|
||||||
|
ReaderState.MAGIC -> magic()
|
||||||
|
ReaderState.HEADER -> header()
|
||||||
|
ReaderState.DATA -> data()
|
||||||
|
else -> ReaderState.WAIT_FOR_DATA
|
||||||
|
}
|
||||||
|
while (s != ReaderState.WAIT_FOR_DATA) {
|
||||||
|
s = when (state) {
|
||||||
|
ReaderState.MAGIC -> magic()
|
||||||
|
ReaderState.HEADER -> header()
|
||||||
|
ReaderState.DATA -> data(flip = false)
|
||||||
|
else -> ReaderState.WAIT_FOR_DATA
|
||||||
}
|
}
|
||||||
when (state) {
|
|
||||||
V3MessageReader.ReaderState.MAGIC -> magic(headerBuffer ?: throw IllegalStateException("header buffer is null"))
|
|
||||||
V3MessageReader.ReaderState.HEADER -> header(headerBuffer ?: throw IllegalStateException("header buffer is null"))
|
|
||||||
V3MessageReader.ReaderState.DATA -> data(dataBuffer ?: throw IllegalStateException("data buffer is null"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun magic(headerBuffer: ByteBuffer) {
|
private fun magic(): ReaderState = if (!findMagicBytes(buffer)) {
|
||||||
if (!findMagicBytes(headerBuffer)) {
|
buffer.compact()
|
||||||
headerBuffer.compact()
|
ReaderState.WAIT_FOR_DATA
|
||||||
return
|
|
||||||
} else {
|
} else {
|
||||||
state = ReaderState.HEADER
|
state = ReaderState.HEADER
|
||||||
header(headerBuffer)
|
ReaderState.HEADER
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun header(headerBuffer: ByteBuffer) {
|
private fun header(): ReaderState {
|
||||||
if (headerBuffer.remaining() < 20) {
|
if (buffer.remaining() < 20) {
|
||||||
headerBuffer.compact()
|
buffer.compact()
|
||||||
headerBuffer.limit(20)
|
return ReaderState.WAIT_FOR_DATA
|
||||||
return
|
|
||||||
}
|
}
|
||||||
command = getCommand(headerBuffer)
|
command = getCommand(buffer)
|
||||||
length = Decode.uint32(headerBuffer).toInt()
|
length = Decode.uint32(buffer).toInt()
|
||||||
if (length > MAX_PAYLOAD_SIZE) {
|
if (length > MAX_PAYLOAD_SIZE) {
|
||||||
throw NodeException("Payload of " + length + " bytes received, no more than " +
|
throw NodeException(
|
||||||
MAX_PAYLOAD_SIZE + " was expected.")
|
"Payload of " + length + " bytes received, no more than " +
|
||||||
|
MAX_PAYLOAD_SIZE + " was expected."
|
||||||
|
)
|
||||||
}
|
}
|
||||||
headerBuffer.get(checksum)
|
buffer.get(checksum)
|
||||||
state = ReaderState.DATA
|
state = ReaderState.DATA
|
||||||
this.headerBuffer = null
|
return ReaderState.DATA
|
||||||
BufferPool.deallocate(headerBuffer)
|
|
||||||
val dataBuffer = BufferPool.allocate(length)
|
|
||||||
this.dataBuffer = dataBuffer
|
|
||||||
dataBuffer.clear()
|
|
||||||
dataBuffer.limit(length)
|
|
||||||
data(dataBuffer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun data(dataBuffer: ByteBuffer) {
|
private fun data(flip: Boolean = true): ReaderState {
|
||||||
if (dataBuffer.position() < length) {
|
if (flip) {
|
||||||
return
|
if (buffer.position() < length) {
|
||||||
|
return ReaderState.WAIT_FOR_DATA
|
||||||
} else {
|
} else {
|
||||||
dataBuffer.flip()
|
buffer.flip()
|
||||||
}
|
}
|
||||||
if (!testChecksum(dataBuffer)) {
|
} else if (buffer.remaining() < length) {
|
||||||
|
buffer.compact()
|
||||||
|
return ReaderState.WAIT_FOR_DATA
|
||||||
|
}
|
||||||
|
if (!testChecksum(buffer)) {
|
||||||
state = ReaderState.MAGIC
|
state = ReaderState.MAGIC
|
||||||
this.dataBuffer = null
|
buffer.clear()
|
||||||
BufferPool.deallocate(dataBuffer)
|
|
||||||
throw NodeException("Checksum failed for message '$command'")
|
throw NodeException("Checksum failed for message '$command'")
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
V3MessageFactory.getPayload(
|
V3MessageFactory.getPayload(
|
||||||
command ?: throw IllegalStateException("command is null"),
|
command ?: throw IllegalStateException("command is null"),
|
||||||
ByteArrayInputStream(dataBuffer.array(),
|
ByteArrayInputStream(
|
||||||
dataBuffer.arrayOffset() + dataBuffer.position(), length),
|
buffer.array(),
|
||||||
|
buffer.arrayOffset() + buffer.position(), length
|
||||||
|
),
|
||||||
length
|
length
|
||||||
)?.let { messages.add(NetworkMessage(it)) }
|
)?.let { messages.add(NetworkMessage(it)) }
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
throw NodeException(e.message)
|
throw NodeException(e.message)
|
||||||
} finally {
|
} finally {
|
||||||
state = ReaderState.MAGIC
|
state = ReaderState.MAGIC
|
||||||
this.dataBuffer = null
|
|
||||||
BufferPool.deallocate(dataBuffer)
|
|
||||||
}
|
}
|
||||||
|
return ReaderState.MAGIC
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMessages(): MutableList<NetworkMessage> {
|
fun getMessages(): MutableList<NetworkMessage> {
|
||||||
@ -163,8 +156,10 @@ class V3MessageReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun testChecksum(buffer: ByteBuffer): Boolean {
|
private fun testChecksum(buffer: ByteBuffer): Boolean {
|
||||||
val payloadChecksum = cryptography().sha512(buffer.array(),
|
val payloadChecksum = cryptography().sha512(
|
||||||
buffer.arrayOffset() + buffer.position(), length)
|
buffer.array(),
|
||||||
|
buffer.arrayOffset() + buffer.position(), length
|
||||||
|
)
|
||||||
for (i in checksum.indices) {
|
for (i in checksum.indices) {
|
||||||
if (checksum[i] != payloadChecksum[i]) {
|
if (checksum[i] != payloadChecksum[i]) {
|
||||||
return false
|
return false
|
||||||
@ -173,17 +168,7 @@ class V3MessageReader {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* De-allocates all buffers. This method should be called iff the reader isn't used anymore, i.e. when its
|
|
||||||
* connection is severed.
|
|
||||||
*/
|
|
||||||
fun cleanup() {
|
|
||||||
state = null
|
|
||||||
headerBuffer?.let { BufferPool.deallocate(it) }
|
|
||||||
dataBuffer?.let { BufferPool.deallocate(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum class ReaderState {
|
private enum class ReaderState {
|
||||||
MAGIC, HEADER, DATA
|
MAGIC, HEADER, DATA, WAIT_FOR_DATA
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,12 +36,16 @@ import javax.crypto.spec.SecretKeySpec
|
|||||||
/**
|
/**
|
||||||
* Implements everything that isn't directly dependent on either Spongy- or Bouncycastle.
|
* Implements everything that isn't directly dependent on either Spongy- or Bouncycastle.
|
||||||
*/
|
*/
|
||||||
abstract class AbstractCryptography protected constructor(@JvmField protected val provider: Provider) : Cryptography, InternalContext.ContextHolder {
|
abstract class AbstractCryptography protected constructor(@JvmField protected val provider: Provider) : Cryptography,
|
||||||
|
InternalContext.ContextHolder {
|
||||||
private lateinit var ctx: InternalContext
|
private lateinit var ctx: InternalContext
|
||||||
|
|
||||||
@JvmField protected val ALGORITHM_ECDSA = "ECDSA"
|
@JvmField
|
||||||
@JvmField protected val ALGORITHM_ECDSA_SHA1 = "SHA1withECDSA"
|
protected val ALGORITHM_ECDSA = "ECDSA"
|
||||||
@JvmField protected val ALGORITHM_EVP_SHA256 = "SHA256withECDSA"
|
@JvmField
|
||||||
|
protected val ALGORITHM_ECDSA_SHA1 = "SHA1withECDSA"
|
||||||
|
@JvmField
|
||||||
|
protected val ALGORITHM_EVP_SHA256 = "SHA256withECDSA"
|
||||||
|
|
||||||
override fun setContext(context: InternalContext) {
|
override fun setContext(context: InternalContext) {
|
||||||
ctx = context
|
ctx = context
|
||||||
@ -65,8 +69,12 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va
|
|||||||
return mda.digest(mda.digest())
|
return mda.digest(mda.digest())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun doubleSha512(data: ByteArray, length: Int): ByteArray {
|
override fun doubleSha512(data: ByteArray, length: Int) = doubleHash("SHA-512", data, length);
|
||||||
val mda = md("SHA-512")
|
|
||||||
|
override fun doubleSha256(data: ByteArray, length: Int) = doubleHash("SHA-256", data, length);
|
||||||
|
|
||||||
|
private fun doubleHash(method: String, data: ByteArray, length: Int): ByteArray {
|
||||||
|
val mda = md(method)
|
||||||
mda.update(data, 0, length)
|
mda.update(data, 0, length)
|
||||||
return mda.digest(mda.digest())
|
return mda.digest(mda.digest())
|
||||||
}
|
}
|
||||||
@ -75,12 +83,6 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va
|
|||||||
return hash("RIPEMD160", *data)
|
return hash("RIPEMD160", *data)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun doubleSha256(data: ByteArray, length: Int): ByteArray {
|
|
||||||
val mda = md("SHA-256")
|
|
||||||
mda.update(data, 0, length)
|
|
||||||
return mda.digest(mda.digest())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun sha1(vararg data: ByteArray): ByteArray {
|
override fun sha1(vararg data: ByteArray): ByteArray {
|
||||||
return hash("SHA-1", *data)
|
return hash("SHA-1", *data)
|
||||||
}
|
}
|
||||||
@ -91,13 +93,17 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun doProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long,
|
override fun doProofOfWork(
|
||||||
extraBytes: Long, callback: ProofOfWorkEngine.Callback) {
|
objectMessage: ObjectMessage, nonceTrialsPerByte: Long,
|
||||||
|
extraBytes: Long, callback: ProofOfWorkEngine.Callback
|
||||||
|
) {
|
||||||
|
|
||||||
val initialHash = getInitialHash(objectMessage)
|
val initialHash = getInitialHash(objectMessage)
|
||||||
|
|
||||||
val target = getProofOfWorkTarget(objectMessage,
|
val target = getProofOfWorkTarget(
|
||||||
max(nonceTrialsPerByte, NETWORK_NONCE_TRIALS_PER_BYTE), max(extraBytes, NETWORK_EXTRA_BYTES))
|
objectMessage,
|
||||||
|
max(nonceTrialsPerByte, NETWORK_NONCE_TRIALS_PER_BYTE), max(extraBytes, NETWORK_EXTRA_BYTES)
|
||||||
|
)
|
||||||
|
|
||||||
ctx.proofOfWorkEngine.calculateNonce(initialHash, target, callback)
|
ctx.proofOfWorkEngine.calculateNonce(initialHash, target, callback)
|
||||||
}
|
}
|
||||||
@ -105,7 +111,10 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va
|
|||||||
@Throws(InsufficientProofOfWorkException::class)
|
@Throws(InsufficientProofOfWorkException::class)
|
||||||
override fun checkProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) {
|
override fun checkProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) {
|
||||||
val target = getProofOfWorkTarget(objectMessage, nonceTrialsPerByte, extraBytes)
|
val target = getProofOfWorkTarget(objectMessage, nonceTrialsPerByte, extraBytes)
|
||||||
val value = doubleSha512(objectMessage.nonce ?: throw ApplicationException("Object without nonce"), getInitialHash(objectMessage))
|
val value = doubleSha512(
|
||||||
|
objectMessage.nonce ?: throw ApplicationException("Object without nonce"),
|
||||||
|
getInitialHash(objectMessage)
|
||||||
|
)
|
||||||
if (Bytes.lt(target, value, 8)) {
|
if (Bytes.lt(target, value, 8)) {
|
||||||
throw InsufficientProofOfWorkException(target, value)
|
throw InsufficientProofOfWorkException(target, value)
|
||||||
}
|
}
|
||||||
@ -136,7 +145,11 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va
|
|||||||
return sha512(objectMessage.payloadBytesWithoutNonce)
|
return sha512(objectMessage.payloadBytesWithoutNonce)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getProofOfWorkTarget(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long): ByteArray {
|
override fun getProofOfWorkTarget(
|
||||||
|
objectMessage: ObjectMessage,
|
||||||
|
nonceTrialsPerByte: Long,
|
||||||
|
extraBytes: Long
|
||||||
|
): ByteArray {
|
||||||
@Suppress("NAME_SHADOWING")
|
@Suppress("NAME_SHADOWING")
|
||||||
val nonceTrialsPerByte = if (nonceTrialsPerByte == 0L) NETWORK_NONCE_TRIALS_PER_BYTE else nonceTrialsPerByte
|
val nonceTrialsPerByte = if (nonceTrialsPerByte == 0L) NETWORK_NONCE_TRIALS_PER_BYTE else nonceTrialsPerByte
|
||||||
@Suppress("NAME_SHADOWING")
|
@Suppress("NAME_SHADOWING")
|
||||||
@ -181,12 +194,16 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createPubkey(version: Long, stream: Long, privateSigningKey: ByteArray, privateEncryptionKey: ByteArray,
|
override fun createPubkey(
|
||||||
nonceTrialsPerByte: Long, extraBytes: Long, vararg features: Pubkey.Feature): Pubkey {
|
version: Long, stream: Long, privateSigningKey: ByteArray, privateEncryptionKey: ByteArray,
|
||||||
return Factory.createPubkey(version, stream,
|
nonceTrialsPerByte: Long, extraBytes: Long, vararg features: Pubkey.Feature
|
||||||
|
): Pubkey {
|
||||||
|
return Factory.createPubkey(
|
||||||
|
version, stream,
|
||||||
createPublicKey(privateSigningKey),
|
createPublicKey(privateSigningKey),
|
||||||
createPublicKey(privateEncryptionKey),
|
createPublicKey(privateEncryptionKey),
|
||||||
nonceTrialsPerByte, extraBytes, *features)
|
nonceTrialsPerByte, extraBytes, *features
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun keyToBigInt(privateKey: ByteArray): BigInteger {
|
override fun keyToBigInt(privateKey: ByteArray): BigInteger {
|
||||||
|
@ -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>
|
||||||
|
}
|
@ -21,8 +21,7 @@ import ch.dissem.bitmessage.entity.BitmessageAddress
|
|||||||
import ch.dissem.bitmessage.entity.Plaintext
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
||||||
import ch.dissem.bitmessage.entity.valueobject.Label
|
import ch.dissem.bitmessage.entity.valueobject.Label
|
||||||
import ch.dissem.bitmessage.exception.ApplicationException
|
import ch.dissem.bitmessage.utils.Collections.single
|
||||||
import ch.dissem.bitmessage.utils.SqlStrings.join
|
|
||||||
import ch.dissem.bitmessage.utils.Strings
|
import ch.dissem.bitmessage.utils.Strings
|
||||||
import ch.dissem.bitmessage.utils.UnixTime
|
import ch.dissem.bitmessage.utils.UnixTime
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@ -110,27 +109,8 @@ abstract class AbstractMessageRepository : MessageRepository, InternalContext.Co
|
|||||||
return find("iv IN (SELECT child FROM Message_Parent WHERE parent=X'${Strings.hex(parent.inventoryVector!!.hash)}')")
|
return find("iv IN (SELECT child FROM Message_Parent WHERE parent=X'${Strings.hex(parent.inventoryVector!!.hash)}')")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getConversation(conversationId: UUID): List<Plaintext> {
|
override fun getConversation(conversationId: UUID, offset: Int, limit: Int): List<Plaintext> {
|
||||||
return find("conversation=X'${conversationId.toString().replace("-", "")}'")
|
return find("conversation=X'${conversationId.toString().replace("-", "")}'", offset, limit)
|
||||||
}
|
|
||||||
|
|
||||||
override fun getLabels(): List<Label> {
|
|
||||||
return findLabels("1=1")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getLabels(vararg types: Label.Type): List<Label> {
|
|
||||||
return findLabels("type IN (${join(*types)})")
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract fun findLabels(where: String): List<Label>
|
|
||||||
|
|
||||||
|
|
||||||
protected fun <T> single(collection: Collection<T>): T? {
|
|
||||||
return when (collection.size) {
|
|
||||||
0 -> null
|
|
||||||
1 -> collection.iterator().next()
|
|
||||||
else -> throw ApplicationException("This shouldn't happen, found ${collection.size} items, one or none was expected")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -156,6 +156,16 @@ interface Cryptography {
|
|||||||
fun doProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long,
|
fun doProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long,
|
||||||
extraBytes: Long, callback: ProofOfWorkEngine.Callback)
|
extraBytes: Long, callback: ProofOfWorkEngine.Callback)
|
||||||
|
|
||||||
|
@JvmSynthetic
|
||||||
|
fun doProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long,
|
||||||
|
extraBytes: Long, callback: (ByteArray, ByteArray) -> Unit) {
|
||||||
|
doProofOfWork(objectMessage, nonceTrialsPerByte, extraBytes, object : ProofOfWorkEngine.Callback {
|
||||||
|
override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) {
|
||||||
|
callback.invoke(initialHash, nonce)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param objectMessage to be checked
|
* @param objectMessage to be checked
|
||||||
* *
|
* *
|
||||||
|
@ -35,9 +35,9 @@ open class DefaultLabeler : Labeler, InternalContext.ContextHolder {
|
|||||||
msg.status = RECEIVED
|
msg.status = RECEIVED
|
||||||
val labelsToAdd =
|
val labelsToAdd =
|
||||||
if (msg.type == BROADCAST) {
|
if (msg.type == BROADCAST) {
|
||||||
ctx.messageRepository.getLabels(Label.Type.BROADCAST, Label.Type.UNREAD)
|
ctx.labelRepository.getLabels(Label.Type.BROADCAST, Label.Type.UNREAD)
|
||||||
} else {
|
} else {
|
||||||
ctx.messageRepository.getLabels(Label.Type.INBOX, Label.Type.UNREAD)
|
ctx.labelRepository.getLabels(Label.Type.INBOX, Label.Type.UNREAD)
|
||||||
}
|
}
|
||||||
msg.addLabels(labelsToAdd)
|
msg.addLabels(labelsToAdd)
|
||||||
listener?.invoke(msg, labelsToAdd, emptyList())
|
listener?.invoke(msg, labelsToAdd, emptyList())
|
||||||
@ -45,7 +45,7 @@ open class DefaultLabeler : Labeler, InternalContext.ContextHolder {
|
|||||||
|
|
||||||
override fun markAsDraft(msg: Plaintext) {
|
override fun markAsDraft(msg: Plaintext) {
|
||||||
msg.status = DRAFT
|
msg.status = DRAFT
|
||||||
val labelsToAdd = ctx.messageRepository.getLabels(Label.Type.DRAFT)
|
val labelsToAdd = ctx.labelRepository.getLabels(Label.Type.DRAFT)
|
||||||
msg.addLabels(labelsToAdd)
|
msg.addLabels(labelsToAdd)
|
||||||
listener?.invoke(msg, labelsToAdd, emptyList())
|
listener?.invoke(msg, labelsToAdd, emptyList())
|
||||||
}
|
}
|
||||||
@ -58,7 +58,7 @@ open class DefaultLabeler : Labeler, InternalContext.ContextHolder {
|
|||||||
}
|
}
|
||||||
val labelsToRemove = msg.labels.filter { it.type == Label.Type.DRAFT }
|
val labelsToRemove = msg.labels.filter { it.type == Label.Type.DRAFT }
|
||||||
msg.removeLabel(Label.Type.DRAFT)
|
msg.removeLabel(Label.Type.DRAFT)
|
||||||
val labelsToAdd = ctx.messageRepository.getLabels(Label.Type.OUTBOX)
|
val labelsToAdd = ctx.labelRepository.getLabels(Label.Type.OUTBOX)
|
||||||
msg.addLabels(labelsToAdd)
|
msg.addLabels(labelsToAdd)
|
||||||
listener?.invoke(msg, labelsToAdd, labelsToRemove)
|
listener?.invoke(msg, labelsToAdd, labelsToRemove)
|
||||||
}
|
}
|
||||||
@ -67,7 +67,7 @@ open class DefaultLabeler : Labeler, InternalContext.ContextHolder {
|
|||||||
msg.status = SENT
|
msg.status = SENT
|
||||||
val labelsToRemove = msg.labels.filter { it.type == Label.Type.OUTBOX }
|
val labelsToRemove = msg.labels.filter { it.type == Label.Type.OUTBOX }
|
||||||
msg.removeLabel(Label.Type.OUTBOX)
|
msg.removeLabel(Label.Type.OUTBOX)
|
||||||
val labelsToAdd = ctx.messageRepository.getLabels(Label.Type.SENT)
|
val labelsToAdd = ctx.labelRepository.getLabels(Label.Type.SENT)
|
||||||
msg.addLabels(labelsToAdd)
|
msg.addLabels(labelsToAdd)
|
||||||
listener?.invoke(msg, labelsToAdd, labelsToRemove)
|
listener?.invoke(msg, labelsToAdd, labelsToRemove)
|
||||||
}
|
}
|
||||||
@ -83,7 +83,7 @@ open class DefaultLabeler : Labeler, InternalContext.ContextHolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun markAsUnread(msg: Plaintext) {
|
override fun markAsUnread(msg: Plaintext) {
|
||||||
val labelsToAdd = ctx.messageRepository.getLabels(Label.Type.UNREAD)
|
val labelsToAdd = ctx.labelRepository.getLabels(Label.Type.UNREAD)
|
||||||
msg.addLabels(labelsToAdd)
|
msg.addLabels(labelsToAdd)
|
||||||
listener?.invoke(msg, labelsToAdd, emptyList())
|
listener?.invoke(msg, labelsToAdd, emptyList())
|
||||||
}
|
}
|
||||||
@ -91,7 +91,7 @@ open class DefaultLabeler : Labeler, InternalContext.ContextHolder {
|
|||||||
override fun delete(msg: Plaintext) {
|
override fun delete(msg: Plaintext) {
|
||||||
val labelsToRemove = msg.labels.toSet()
|
val labelsToRemove = msg.labels.toSet()
|
||||||
msg.labels.clear()
|
msg.labels.clear()
|
||||||
val labelsToAdd = ctx.messageRepository.getLabels(Label.Type.TRASH)
|
val labelsToAdd = ctx.labelRepository.getLabels(Label.Type.TRASH)
|
||||||
msg.addLabels(labelsToAdd)
|
msg.addLabels(labelsToAdd)
|
||||||
listener?.invoke(msg, labelsToAdd, labelsToRemove)
|
listener?.invoke(msg, labelsToAdd, labelsToRemove)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
@ -24,12 +24,6 @@ import ch.dissem.bitmessage.entity.valueobject.Label
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
interface MessageRepository {
|
interface MessageRepository {
|
||||||
fun getLabels(): List<Label>
|
|
||||||
|
|
||||||
fun getLabels(vararg types: Label.Type): List<Label>
|
|
||||||
|
|
||||||
fun save(label: Label)
|
|
||||||
|
|
||||||
fun countUnread(label: Label?): Int
|
fun countUnread(label: Label?): Int
|
||||||
|
|
||||||
fun getAllMessages(): List<Plaintext>
|
fun getAllMessages(): List<Plaintext>
|
||||||
@ -47,7 +41,7 @@ interface MessageRepository {
|
|||||||
* *
|
* *
|
||||||
* @return a distinct list of all conversations that have at least one message with the given label.
|
* @return a distinct list of all conversations that have at least one message with the given label.
|
||||||
*/
|
*/
|
||||||
fun findConversations(label: Label?): List<UUID>
|
fun findConversations(label: Label?, offset: Int = 0, limit: Int = 0): List<UUID>
|
||||||
|
|
||||||
fun findMessages(label: Label?): List<Plaintext>
|
fun findMessages(label: Label?): List<Plaintext>
|
||||||
|
|
||||||
@ -74,5 +68,5 @@ interface MessageRepository {
|
|||||||
* *
|
* *
|
||||||
* @return all messages with the given conversation ID
|
* @return all messages with the given conversation ID
|
||||||
*/
|
*/
|
||||||
fun getConversation(conversationId: UUID): Collection<Plaintext>
|
fun getConversation(conversationId: UUID, offset: Int = 0, limit: Int = 0): Collection<Plaintext>
|
||||||
}
|
}
|
||||||
|
@ -31,4 +31,13 @@ interface NodeRegistry {
|
|||||||
fun getKnownAddresses(limit: Int, vararg streams: Long): List<NetworkAddress>
|
fun getKnownAddresses(limit: Int, vararg streams: Long): List<NetworkAddress>
|
||||||
|
|
||||||
fun offerAddresses(nodes: List<NetworkAddress>)
|
fun offerAddresses(nodes: List<NetworkAddress>)
|
||||||
|
|
||||||
|
fun update(node: NetworkAddress)
|
||||||
|
|
||||||
|
fun remove(node: NetworkAddress)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove stale nodes
|
||||||
|
*/
|
||||||
|
fun cleanup()
|
||||||
}
|
}
|
||||||
|
@ -31,8 +31,8 @@ object NodeRegistryHelper {
|
|||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun loadStableNodes(): Map<Long, Set<NetworkAddress>> {
|
fun loadStableNodes(): Map<Long, Set<NetworkAddress>> {
|
||||||
javaClass.classLoader.getResourceAsStream("nodes.txt").use { `in` ->
|
javaClass.classLoader.getResourceAsStream("nodes.txt").use { input ->
|
||||||
val scanner = Scanner(`in`)
|
val scanner = Scanner(input)
|
||||||
var stream: Long = 0
|
var stream: Long = 0
|
||||||
val result = HashMap<Long, Set<NetworkAddress>>()
|
val result = HashMap<Long, Set<NetworkAddress>>()
|
||||||
var streamSet: MutableSet<NetworkAddress>? = null
|
var streamSet: MutableSet<NetworkAddress>? = null
|
||||||
@ -41,7 +41,7 @@ object NodeRegistryHelper {
|
|||||||
val line = scanner.nextLine().trim { it <= ' ' }
|
val line = scanner.nextLine().trim { it <= ' ' }
|
||||||
if (line.startsWith("[stream")) {
|
if (line.startsWith("[stream")) {
|
||||||
stream = java.lang.Long.parseLong(line.substring(8, line.lastIndexOf(']')))
|
stream = java.lang.Long.parseLong(line.substring(8, line.lastIndexOf(']')))
|
||||||
streamSet = HashSet<NetworkAddress>()
|
streamSet = HashSet()
|
||||||
result.put(stream, streamSet)
|
result.put(stream, streamSet)
|
||||||
} else if (streamSet != null && !line.isEmpty() && !line.startsWith("#")) {
|
} else if (streamSet != null && !line.isEmpty() && !line.startsWith("#")) {
|
||||||
val portIndex = line.lastIndexOf(':')
|
val portIndex = line.lastIndexOf(':')
|
||||||
|
@ -23,16 +23,32 @@ interface ProofOfWorkEngine {
|
|||||||
/**
|
/**
|
||||||
* Returns a nonce, such that the first 8 bytes from sha512(sha512(nonce||initialHash)) represent a unsigned long
|
* Returns a nonce, such that the first 8 bytes from sha512(sha512(nonce||initialHash)) represent a unsigned long
|
||||||
* smaller than target.
|
* smaller than target.
|
||||||
|
*
|
||||||
* @param initialHash the SHA-512 hash of the object to send, sans nonce
|
* @param initialHash the SHA-512 hash of the object to send, sans nonce
|
||||||
* *
|
|
||||||
* @param target the target, representing an unsigned long
|
* @param target the target, representing an unsigned long
|
||||||
* *
|
* @param callback called with the initial hash and the calculated nonce as argument. The ProofOfWorkEngine
|
||||||
* @param callback called with the calculated nonce as argument. The ProofOfWorkEngine implementation must make
|
* implementation must make sure this is only called once.
|
||||||
* * sure this is only called once.
|
|
||||||
*/
|
*/
|
||||||
fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: Callback)
|
fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: Callback)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a nonce, such that the first 8 bytes from sha512(sha512(nonce||initialHash)) represent a unsigned long
|
||||||
|
* smaller than target.
|
||||||
|
*
|
||||||
|
* @param initialHash the SHA-512 hash of the object to send, sans nonce
|
||||||
|
* @param target the target, representing an unsigned long
|
||||||
|
* @param callback called with the initial hash and the calculated nonce as argument. The ProofOfWorkEngine
|
||||||
|
* implementation must make sure this is only called once.
|
||||||
|
*/
|
||||||
|
@JvmSynthetic
|
||||||
|
fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: (ByteArray, ByteArray) -> Unit) {
|
||||||
|
calculateNonce(initialHash, target, object : Callback {
|
||||||
|
override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) {
|
||||||
|
callback.invoke(initialHash, nonce)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
interface Callback {
|
interface Callback {
|
||||||
/**
|
/**
|
||||||
* @param nonce 8 bytes nonce
|
* @param nonce 8 bytes nonce
|
||||||
|
@ -641,24 +641,26 @@ private class Encoder(val options: Options, output: ByteArray) : Coder(output) {
|
|||||||
* Lookup table for turning Base64 alphabet positions (6 bits)
|
* Lookup table for turning Base64 alphabet positions (6 bits)
|
||||||
* into output bytes.
|
* into output bytes.
|
||||||
*/
|
*/
|
||||||
private val ENCODE = charArrayOf(
|
private val ENCODE = charsAsBytes(
|
||||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
|
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
|
||||||
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
|
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
|
||||||
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
||||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
|
||||||
).map { it.toByte() }.toByteArray()
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lookup table for turning Base64 alphabet positions (6 bits)
|
* Lookup table for turning Base64 alphabet positions (6 bits)
|
||||||
* into output bytes.
|
* into output bytes.
|
||||||
*/
|
*/
|
||||||
private val ENCODE_WEBSAFE = charArrayOf(
|
private val ENCODE_WEBSAFE = charsAsBytes(
|
||||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
|
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
|
||||||
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
|
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
|
||||||
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
||||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'
|
||||||
).map { it.toByte() }.toByteArray()
|
)
|
||||||
|
|
||||||
|
private fun charsAsBytes(vararg elements: Char): ByteArray = elements.map { it.toByte() }.toByteArray()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package ch.dissem.bitmessage.utils
|
package ch.dissem.bitmessage.utils
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.exception.ApplicationException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
object Collections {
|
object Collections {
|
||||||
@ -67,4 +68,12 @@ object Collections {
|
|||||||
}
|
}
|
||||||
throw IllegalArgumentException("Empty collection? Size: " + collection.size)
|
throw IllegalArgumentException("Empty collection? Size: " + collection.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic fun <T> single(collection: Collection<T>): T? {
|
||||||
|
return when (collection.size) {
|
||||||
|
0 -> null
|
||||||
|
1 -> collection.iterator().next()
|
||||||
|
else -> throw ApplicationException("This shouldn't happen, found ${collection.size} items, one or none was expected")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,10 +16,11 @@
|
|||||||
|
|
||||||
package ch.dissem.bitmessage.utils
|
package ch.dissem.bitmessage.utils
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.entity.Conversation
|
||||||
import ch.dissem.bitmessage.entity.Plaintext
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.Label
|
||||||
import ch.dissem.bitmessage.ports.MessageRepository
|
import ch.dissem.bitmessage.ports.MessageRepository
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
@ -30,7 +31,11 @@ import java.util.regex.Pattern.CASE_INSENSITIVE
|
|||||||
*/
|
*/
|
||||||
class ConversationService(private val messageRepository: MessageRepository) {
|
class ConversationService(private val messageRepository: MessageRepository) {
|
||||||
|
|
||||||
private val SUBJECT_PREFIX = Pattern.compile("^(re|fwd?):", CASE_INSENSITIVE)
|
private val SUBJECT_PREFIX = Pattern.compile("^(re|fwd?):\\s*", CASE_INSENSITIVE)
|
||||||
|
|
||||||
|
fun findConversations(label: Label?, offset: Int = 0, limit: Int = 0, conversationLimit: Int = 10) =
|
||||||
|
messageRepository.findConversations(label, offset, limit)
|
||||||
|
.map { getConversation(it, conversationLimit) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the whole conversation from one single message. If the message isn't part
|
* Retrieve the whole conversation from one single message. If the message isn't part
|
||||||
@ -41,7 +46,7 @@ class ConversationService(private val messageRepository: MessageRepository) {
|
|||||||
* *
|
* *
|
||||||
* @return a list of messages that belong to the same conversation.
|
* @return a list of messages that belong to the same conversation.
|
||||||
*/
|
*/
|
||||||
fun getConversation(message: Plaintext): List<Plaintext> {
|
fun getConversation(message: Plaintext): Conversation {
|
||||||
return getConversation(message.conversationId)
|
return getConversation(message.conversationId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,8 +63,8 @@ class ConversationService(private val messageRepository: MessageRepository) {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getConversation(conversationId: UUID): List<Plaintext> {
|
fun getConversation(conversationId: UUID, limit: Int = 0): Conversation {
|
||||||
val messages = sorted(messageRepository.getConversation(conversationId))
|
val messages = sorted(messageRepository.getConversation(conversationId, 0, limit))
|
||||||
val map = HashMap<InventoryVector, Plaintext>(messages.size)
|
val map = HashMap<InventoryVector, Plaintext>(messages.size)
|
||||||
for (message in messages) {
|
for (message in messages) {
|
||||||
message.inventoryVector?.let {
|
message.inventoryVector?.let {
|
||||||
@ -74,7 +79,7 @@ class ConversationService(private val messageRepository: MessageRepository) {
|
|||||||
result.add(pos, last)
|
result.add(pos, last)
|
||||||
addAncestors(last, result, messages, map)
|
addAncestors(last, result, messages, map)
|
||||||
}
|
}
|
||||||
return result
|
return Conversation(conversationId, getSubject(result) ?: "", result)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSubject(conversation: List<Plaintext>): String? {
|
fun getSubject(conversation: List<Plaintext>): String? {
|
||||||
@ -109,7 +114,12 @@ class ConversationService(private val messageRepository: MessageRepository) {
|
|||||||
return child.parents.firstOrNull { it == item.inventoryVector } != null
|
return child.parents.firstOrNull { it == item.inventoryVector } != null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addAncestors(message: Plaintext, result: LinkedList<Plaintext>, messages: LinkedList<Plaintext>, map: MutableMap<InventoryVector, Plaintext>) {
|
private fun addAncestors(
|
||||||
|
message: Plaintext,
|
||||||
|
result: LinkedList<Plaintext>,
|
||||||
|
messages: LinkedList<Plaintext>,
|
||||||
|
map: MutableMap<InventoryVector, Plaintext>
|
||||||
|
) {
|
||||||
for (parentKey in message.parents) {
|
for (parentKey in message.parents) {
|
||||||
map.remove(parentKey)?.let {
|
map.remove(parentKey)?.let {
|
||||||
messages.remove(it)
|
messages.remove(it)
|
||||||
|
@ -29,7 +29,7 @@ object DebugUtils {
|
|||||||
try {
|
try {
|
||||||
val f = File(System.getProperty("user.home") + "/jabit.error/" + objectMessage.inventoryVector + ".inv")
|
val f = File(System.getProperty("user.home") + "/jabit.error/" + objectMessage.inventoryVector + ".inv")
|
||||||
f.createNewFile()
|
f.createNewFile()
|
||||||
objectMessage.write(FileOutputStream(f))
|
objectMessage.writer().write(FileOutputStream(f))
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
LOG.debug(e.message, e)
|
LOG.debug(e.message, e)
|
||||||
}
|
}
|
||||||
|
@ -25,21 +25,21 @@ import java.nio.ByteBuffer
|
|||||||
* https://bitmessage.org/wiki/Protocol_specification#Common_structures
|
* https://bitmessage.org/wiki/Protocol_specification#Common_structures
|
||||||
*/
|
*/
|
||||||
object Decode {
|
object Decode {
|
||||||
@JvmStatic fun shortVarBytes(`in`: InputStream, counter: AccessCounter): ByteArray {
|
@JvmStatic fun shortVarBytes(input: InputStream, counter: AccessCounter): ByteArray {
|
||||||
val length = uint16(`in`, counter)
|
val length = uint16(input, counter)
|
||||||
return bytes(`in`, length, counter)
|
return bytes(input, length, counter)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic @JvmOverloads fun varBytes(`in`: InputStream, counter: AccessCounter? = null): ByteArray {
|
@JvmStatic @JvmOverloads fun varBytes(input: InputStream, counter: AccessCounter? = null): ByteArray {
|
||||||
val length = varInt(`in`, counter).toInt()
|
val length = varInt(input, counter).toInt()
|
||||||
return bytes(`in`, length, counter)
|
return bytes(input, length, counter)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic @JvmOverloads fun bytes(`in`: InputStream, count: Int, counter: AccessCounter? = null): ByteArray {
|
@JvmStatic @JvmOverloads fun bytes(input: InputStream, count: Int, counter: AccessCounter? = null): ByteArray {
|
||||||
val result = ByteArray(count)
|
val result = ByteArray(count)
|
||||||
var off = 0
|
var off = 0
|
||||||
while (off < count) {
|
while (off < count) {
|
||||||
val read = `in`.read(result, off, count - off)
|
val read = input.read(result, off, count - off)
|
||||||
if (read < 0) {
|
if (read < 0) {
|
||||||
throw IOException("Unexpected end of stream, wanted to read $count bytes but only got $off")
|
throw IOException("Unexpected end of stream, wanted to read $count bytes but only got $off")
|
||||||
}
|
}
|
||||||
@ -49,60 +49,58 @@ object Decode {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic fun varIntList(`in`: InputStream): LongArray {
|
@JvmStatic fun varIntList(input: InputStream): LongArray {
|
||||||
val length = varInt(`in`).toInt()
|
val length = varInt(input).toInt()
|
||||||
val result = LongArray(length)
|
val result = LongArray(length)
|
||||||
|
|
||||||
for (i in 0..length - 1) {
|
for (i in 0 until length) {
|
||||||
result[i] = varInt(`in`)
|
result[i] = varInt(input)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic @JvmOverloads fun varInt(`in`: InputStream, counter: AccessCounter? = null): Long {
|
@JvmStatic @JvmOverloads fun varInt(input: InputStream, counter: AccessCounter? = null): Long {
|
||||||
val first = `in`.read()
|
val first = input.read()
|
||||||
AccessCounter.inc(counter)
|
AccessCounter.inc(counter)
|
||||||
when (first) {
|
when (first) {
|
||||||
0xfd -> return uint16(`in`, counter).toLong()
|
0xfd -> return uint16(input, counter).toLong()
|
||||||
0xfe -> return uint32(`in`, counter)
|
0xfe -> return uint32(input, counter)
|
||||||
0xff -> return int64(`in`, counter)
|
0xff -> return int64(input, counter)
|
||||||
else -> return first.toLong()
|
else -> return first.toLong()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic fun uint8(`in`: InputStream): Int {
|
@JvmStatic fun uint8(input: InputStream): Int = input.read()
|
||||||
return `in`.read()
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic @JvmOverloads fun uint16(`in`: InputStream, counter: AccessCounter? = null): Int {
|
@JvmStatic @JvmOverloads fun uint16(input: InputStream, counter: AccessCounter? = null): Int {
|
||||||
AccessCounter.inc(counter, 2)
|
AccessCounter.inc(counter, 2)
|
||||||
return `in`.read() shl 8 or `in`.read()
|
return input.read() shl 8 or input.read()
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic @JvmOverloads fun uint32(`in`: InputStream, counter: AccessCounter? = null): Long {
|
@JvmStatic @JvmOverloads fun uint32(input: InputStream, counter: AccessCounter? = null): Long {
|
||||||
AccessCounter.inc(counter, 4)
|
AccessCounter.inc(counter, 4)
|
||||||
return (`in`.read() shl 24 or (`in`.read() shl 16) or (`in`.read() shl 8) or `in`.read()).toLong()
|
return (input.read() shl 24 or (input.read() shl 16) or (input.read() shl 8) or input.read()).toLong()
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic fun uint32(`in`: ByteBuffer): Long {
|
@JvmStatic fun uint32(input: ByteBuffer): Long {
|
||||||
return (u(`in`.get()) shl 24 or (u(`in`.get()) shl 16) or (u(`in`.get()) shl 8) or u(`in`.get())).toLong()
|
return (u(input.get()) shl 24 or (u(input.get()) shl 16) or (u(input.get()) shl 8) or u(input.get())).toLong()
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic @JvmOverloads fun int32(`in`: InputStream, counter: AccessCounter? = null): Int {
|
@JvmStatic @JvmOverloads fun int32(input: InputStream, counter: AccessCounter? = null): Int {
|
||||||
AccessCounter.inc(counter, 4)
|
AccessCounter.inc(counter, 4)
|
||||||
return ByteBuffer.wrap(bytes(`in`, 4)).int
|
return ByteBuffer.wrap(bytes(input, 4)).int
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic @JvmOverloads fun int64(`in`: InputStream, counter: AccessCounter? = null): Long {
|
@JvmStatic @JvmOverloads fun int64(input: InputStream, counter: AccessCounter? = null): Long {
|
||||||
AccessCounter.inc(counter, 8)
|
AccessCounter.inc(counter, 8)
|
||||||
return ByteBuffer.wrap(bytes(`in`, 8)).long
|
return ByteBuffer.wrap(bytes(input, 8)).long
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic @JvmOverloads fun varString(`in`: InputStream, counter: AccessCounter? = null): String {
|
@JvmStatic @JvmOverloads fun varString(input: InputStream, counter: AccessCounter? = null): String {
|
||||||
val length = varInt(`in`, counter).toInt()
|
val length = varInt(input, counter).toInt()
|
||||||
// technically, it says the length in characters, but I think this one might be correct
|
// technically, it says the length in characters, but I think this one might be correct
|
||||||
// otherwise it will get complicated, as we'll need to read UTF-8 char by char...
|
// otherwise it will get complicated, as we'll need to read UTF-8 char by char...
|
||||||
return String(bytes(`in`, length, counter))
|
return String(bytes(input, length, counter))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -26,53 +26,63 @@ import java.nio.ByteBuffer
|
|||||||
* https://bitmessage.org/wiki/Protocol_specification#Common_structures
|
* https://bitmessage.org/wiki/Protocol_specification#Common_structures
|
||||||
*/
|
*/
|
||||||
object Encode {
|
object Encode {
|
||||||
@JvmStatic fun varIntList(values: LongArray, stream: OutputStream) {
|
@JvmStatic
|
||||||
|
fun varIntList(values: LongArray, stream: OutputStream) {
|
||||||
varInt(values.size, stream)
|
varInt(values.size, stream)
|
||||||
for (value in values) {
|
for (value in values) {
|
||||||
varInt(value, stream)
|
varInt(value, stream)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic fun varIntList(values: LongArray, buffer: ByteBuffer) {
|
@JvmStatic
|
||||||
|
fun varIntList(values: LongArray, buffer: ByteBuffer) {
|
||||||
varInt(values.size, buffer)
|
varInt(values.size, buffer)
|
||||||
for (value in values) {
|
for (value in values) {
|
||||||
varInt(value, buffer)
|
varInt(value, buffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic fun varInt(value: Int, buffer: ByteBuffer) = varInt(value.toLong(), buffer)
|
@JvmStatic
|
||||||
@JvmStatic fun varInt(value: Long, buffer: ByteBuffer) {
|
fun varInt(value: Number, buffer: ByteBuffer) {
|
||||||
if (value < 0) {
|
val longValue = value.toLong()
|
||||||
|
when {
|
||||||
|
longValue < 0 -> {
|
||||||
// This is due to the fact that Java doesn't really support unsigned values.
|
// This is due to the fact that Java doesn't really support unsigned values.
|
||||||
// Please be aware that this might be an error due to a smaller negative value being cast to long.
|
// Please be aware that this might be an error due to a smaller negative value being cast to long.
|
||||||
// Normally, negative values shouldn't occur within the protocol, and longs large enough for being
|
// Normally, negative values shouldn't occur within the protocol, and longs large enough for being
|
||||||
// recognized as negatives aren't realistic.
|
// recognized as negatives aren't realistic.
|
||||||
buffer.put(0xff.toByte())
|
buffer.put(0xff.toByte())
|
||||||
buffer.putLong(value)
|
buffer.putLong(longValue)
|
||||||
} else if (value < 0xfd) {
|
}
|
||||||
|
longValue < 0xfd -> {
|
||||||
buffer.put(value.toByte())
|
buffer.put(value.toByte())
|
||||||
} else if (value <= 0xffffL) {
|
}
|
||||||
|
longValue <= 0xffffL -> {
|
||||||
buffer.put(0xfd.toByte())
|
buffer.put(0xfd.toByte())
|
||||||
buffer.putShort(value.toShort())
|
buffer.putShort(value.toShort())
|
||||||
} else if (value <= 0xffffffffL) {
|
}
|
||||||
|
longValue <= 0xffffffffL -> {
|
||||||
buffer.put(0xfe.toByte())
|
buffer.put(0xfe.toByte())
|
||||||
buffer.putInt(value.toInt())
|
buffer.putInt(value.toInt())
|
||||||
} else {
|
}
|
||||||
|
else -> {
|
||||||
buffer.put(0xff.toByte())
|
buffer.put(0xff.toByte())
|
||||||
buffer.putLong(value)
|
buffer.putLong(longValue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic fun varInt(value: Int) = varInt(value.toLong())
|
@JvmStatic
|
||||||
@JvmStatic fun varInt(value: Long): ByteArray {
|
fun varInt(value: Number): ByteArray {
|
||||||
val buffer = ByteBuffer.allocate(9)
|
val buffer = ByteBuffer.allocate(9)
|
||||||
varInt(value, buffer)
|
varInt(value, buffer)
|
||||||
buffer.flip()
|
buffer.flip()
|
||||||
return Bytes.truncate(buffer.array(), buffer.limit())
|
return Bytes.truncate(buffer.array(), buffer.limit())
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic @JvmOverloads fun varInt(value: Int, stream: OutputStream, counter: AccessCounter? = null) = varInt(value.toLong(), stream, counter)
|
@JvmStatic
|
||||||
@JvmStatic @JvmOverloads fun varInt(value: Long, stream: OutputStream, counter: AccessCounter? = null) {
|
@JvmOverloads
|
||||||
|
fun varInt(value: Number, stream: OutputStream, counter: AccessCounter? = null) {
|
||||||
val buffer = ByteBuffer.allocate(9)
|
val buffer = ByteBuffer.allocate(9)
|
||||||
varInt(value, buffer)
|
varInt(value, buffer)
|
||||||
buffer.flip()
|
buffer.flip()
|
||||||
@ -80,46 +90,51 @@ object Encode {
|
|||||||
AccessCounter.inc(counter, buffer.limit())
|
AccessCounter.inc(counter, buffer.limit())
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic @JvmOverloads fun int8(value: Long, stream: OutputStream, counter: AccessCounter? = null) = int8(value.toInt(), stream, counter)
|
@JvmStatic
|
||||||
@JvmStatic @JvmOverloads fun int8(value: Int, stream: OutputStream, counter: AccessCounter? = null) {
|
@JvmOverloads
|
||||||
stream.write(value)
|
fun int8(value: Number, stream: OutputStream, counter: AccessCounter? = null) {
|
||||||
|
stream.write(value.toInt())
|
||||||
AccessCounter.inc(counter)
|
AccessCounter.inc(counter)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic @JvmOverloads fun int16(value: Long, stream: OutputStream, counter: AccessCounter? = null) = int16(value.toShort(), stream, counter)
|
@JvmStatic
|
||||||
@JvmStatic @JvmOverloads fun int16(value: Int, stream: OutputStream, counter: AccessCounter? = null) = int16(value.toShort(), stream, counter)
|
@JvmOverloads
|
||||||
@JvmStatic @JvmOverloads fun int16(value: Short, stream: OutputStream, counter: AccessCounter? = null) {
|
fun int16(value: Number, stream: OutputStream, counter: AccessCounter? = null) {
|
||||||
stream.write(ByteBuffer.allocate(2).putShort(value).array())
|
stream.write(ByteBuffer.allocate(2).putShort(value.toShort()).array())
|
||||||
AccessCounter.inc(counter, 2)
|
AccessCounter.inc(counter, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic fun int16(value: Long, buffer: ByteBuffer) = int16(value.toShort(), buffer)
|
@JvmStatic
|
||||||
@JvmStatic fun int16(value: Int, buffer: ByteBuffer) = int16(value.toShort(), buffer)
|
fun int16(value: Number, buffer: ByteBuffer) {
|
||||||
@JvmStatic fun int16(value: Short, buffer: ByteBuffer) {
|
buffer.putShort(value.toShort())
|
||||||
buffer.putShort(value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic @JvmOverloads fun int32(value: Long, stream: OutputStream, counter: AccessCounter? = null) = int32(value.toInt(), stream, counter)
|
@JvmStatic
|
||||||
@JvmStatic @JvmOverloads fun int32(value: Int, stream: OutputStream, counter: AccessCounter? = null) {
|
@JvmOverloads
|
||||||
stream.write(ByteBuffer.allocate(4).putInt(value).array())
|
fun int32(value: Number, stream: OutputStream, counter: AccessCounter? = null) {
|
||||||
|
stream.write(ByteBuffer.allocate(4).putInt(value.toInt()).array())
|
||||||
AccessCounter.inc(counter, 4)
|
AccessCounter.inc(counter, 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic fun int32(value: Long, buffer: ByteBuffer) = int32(value.toInt(), buffer)
|
@JvmStatic
|
||||||
@JvmStatic fun int32(value: Int, buffer: ByteBuffer) {
|
fun int32(value: Number, buffer: ByteBuffer) {
|
||||||
buffer.putInt(value)
|
buffer.putInt(value.toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic @JvmOverloads fun int64(value: Long, stream: OutputStream, counter: AccessCounter? = null) {
|
@JvmStatic
|
||||||
stream.write(ByteBuffer.allocate(8).putLong(value).array())
|
@JvmOverloads
|
||||||
|
fun int64(value: Number, stream: OutputStream, counter: AccessCounter? = null) {
|
||||||
|
stream.write(ByteBuffer.allocate(8).putLong(value.toLong()).array())
|
||||||
AccessCounter.inc(counter, 8)
|
AccessCounter.inc(counter, 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic fun int64(value: Long, buffer: ByteBuffer) {
|
@JvmStatic
|
||||||
buffer.putLong(value)
|
fun int64(value: Number, buffer: ByteBuffer) {
|
||||||
|
buffer.putLong(value.toLong())
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic fun varString(value: String, out: OutputStream) {
|
@JvmStatic
|
||||||
|
fun varString(value: String, out: OutputStream) {
|
||||||
val bytes = value.toByteArray(charset("utf-8"))
|
val bytes = value.toByteArray(charset("utf-8"))
|
||||||
// Technically, it says the length in characters, but I think this one might be correct.
|
// Technically, it says the length in characters, but I think this one might be correct.
|
||||||
// It doesn't really matter, as only ASCII characters are being used.
|
// It doesn't really matter, as only ASCII characters are being used.
|
||||||
@ -128,7 +143,8 @@ object Encode {
|
|||||||
out.write(bytes)
|
out.write(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic fun varString(value: String, buffer: ByteBuffer) {
|
@JvmStatic
|
||||||
|
fun varString(value: String, buffer: ByteBuffer) {
|
||||||
val bytes = value.toByteArray()
|
val bytes = value.toByteArray()
|
||||||
// Technically, it says the length in characters, but I think this one might be correct.
|
// Technically, it says the length in characters, but I think this one might be correct.
|
||||||
// It doesn't really matter, as only ASCII characters are being used.
|
// It doesn't really matter, as only ASCII characters are being used.
|
||||||
@ -137,12 +153,14 @@ object Encode {
|
|||||||
buffer.put(bytes)
|
buffer.put(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic fun varBytes(data: ByteArray, out: OutputStream) {
|
@JvmStatic
|
||||||
|
fun varBytes(data: ByteArray, out: OutputStream) {
|
||||||
varInt(data.size.toLong(), out)
|
varInt(data.size.toLong(), out)
|
||||||
out.write(data)
|
out.write(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic fun varBytes(data: ByteArray, buffer: ByteBuffer) {
|
@JvmStatic
|
||||||
|
fun varBytes(data: ByteArray, buffer: ByteBuffer) {
|
||||||
varInt(data.size.toLong(), buffer)
|
varInt(data.size.toLong(), buffer)
|
||||||
buffer.put(data)
|
buffer.put(data)
|
||||||
}
|
}
|
||||||
@ -152,9 +170,10 @@ object Encode {
|
|||||||
* @param streamable the object to be serialized
|
* @param streamable the object to be serialized
|
||||||
* @return an array of bytes representing the given streamable object.
|
* @return an array of bytes representing the given streamable object.
|
||||||
*/
|
*/
|
||||||
@JvmStatic fun bytes(streamable: Streamable): ByteArray {
|
@JvmStatic
|
||||||
|
fun bytes(streamable: Streamable): ByteArray {
|
||||||
val stream = ByteArrayOutputStream()
|
val stream = ByteArrayOutputStream()
|
||||||
streamable.write(stream)
|
streamable.writer().write(stream)
|
||||||
return stream.toByteArray()
|
return stream.toByteArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,9 +182,10 @@ object Encode {
|
|||||||
* @param padding the result will be padded such that its length is a multiple of *padding*
|
* @param padding the result will be padded such that its length is a multiple of *padding*
|
||||||
* @return the bytes of the given [Streamable] object, 0-padded such that the final length is x*padding.
|
* @return the bytes of the given [Streamable] object, 0-padded such that the final length is x*padding.
|
||||||
*/
|
*/
|
||||||
@JvmStatic fun bytes(streamable: Streamable, padding: Int): ByteArray {
|
@JvmStatic
|
||||||
|
fun bytes(streamable: Streamable, padding: Int): ByteArray {
|
||||||
val stream = ByteArrayOutputStream()
|
val stream = ByteArrayOutputStream()
|
||||||
streamable.write(stream)
|
streamable.writer().write(stream)
|
||||||
val offset = padding - stream.size() % padding
|
val offset = padding - stream.size() % padding
|
||||||
val length = stream.size() + offset
|
val length = stream.size() + offset
|
||||||
val result = ByteArray(length)
|
val result = ByteArray(length)
|
||||||
|
@ -35,11 +35,9 @@ import ch.dissem.bitmessage.utils.TTL
|
|||||||
import ch.dissem.bitmessage.utils.TestUtils
|
import ch.dissem.bitmessage.utils.TestUtils
|
||||||
import ch.dissem.bitmessage.utils.UnixTime.MINUTE
|
import ch.dissem.bitmessage.utils.UnixTime.MINUTE
|
||||||
import com.nhaarman.mockito_kotlin.*
|
import com.nhaarman.mockito_kotlin.*
|
||||||
import org.hamcrest.CoreMatchers.`is`
|
import org.junit.jupiter.api.Assertions.*
|
||||||
import org.hamcrest.CoreMatchers.notNullValue
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.Assert.*
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Test
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
@ -47,15 +45,16 @@ import kotlin.concurrent.thread
|
|||||||
* @author Christian Basler
|
* @author Christian Basler
|
||||||
*/
|
*/
|
||||||
class BitmessageContextTest {
|
class BitmessageContextTest {
|
||||||
private var listener: BitmessageContext.Listener = mock()
|
private var testListener: BitmessageContext.Listener = mock()
|
||||||
private val inventory = spy(TestInventory())
|
private val testInventory = spy(TestInventory())
|
||||||
private val testPowRepo = spy(object : ProofOfWorkRepository {
|
private val testPowRepo = spy(object : ProofOfWorkRepository {
|
||||||
internal var items: MutableMap<InventoryVector, ProofOfWorkRepository.Item> = HashMap()
|
internal var items: MutableMap<InventoryVector, ProofOfWorkRepository.Item> = HashMap()
|
||||||
internal var added = 0
|
internal var added = 0
|
||||||
internal var removed = 0
|
internal var removed = 0
|
||||||
|
|
||||||
override fun getItem(initialHash: ByteArray): ProofOfWorkRepository.Item {
|
override fun getItem(initialHash: ByteArray): ProofOfWorkRepository.Item {
|
||||||
return items[InventoryVector(initialHash)] ?: throw IllegalArgumentException("${hex(initialHash)} not found in $items")
|
return items[InventoryVector(initialHash)]
|
||||||
|
?: throw IllegalArgumentException("${hex(initialHash)} not found in $items")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItems(): List<ByteArray> {
|
override fun getItems(): List<ByteArray> {
|
||||||
@ -72,7 +71,10 @@ class BitmessageContextTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun putObject(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) {
|
override fun putObject(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) {
|
||||||
items.put(InventoryVector(cryptography().getInitialHash(objectMessage)), ProofOfWorkRepository.Item(objectMessage, nonceTrialsPerByte, extraBytes))
|
items.put(
|
||||||
|
InventoryVector(cryptography().getInitialHash(objectMessage)),
|
||||||
|
ProofOfWorkRepository.Item(objectMessage, nonceTrialsPerByte, extraBytes)
|
||||||
|
)
|
||||||
added++
|
added++
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,26 +95,27 @@ class BitmessageContextTest {
|
|||||||
thread { callback.onNonceCalculated(initialHash, ByteArray(8)) }
|
thread { callback.onNonceCalculated(initialHash, ByteArray(8)) }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
private var ctx = BitmessageContext.Builder()
|
private var ctx = BitmessageContext.build {
|
||||||
.addressRepo(mock())
|
addressRepo = mock()
|
||||||
.cryptography(BouncyCryptography())
|
cryptography = BouncyCryptography()
|
||||||
.inventory(inventory)
|
inventory = testInventory
|
||||||
.listener(listener)
|
listener = testListener
|
||||||
.messageRepo(mock())
|
labelRepo = mock()
|
||||||
.networkHandler(mock {
|
messageRepo = mock()
|
||||||
|
networkHandler = mock {
|
||||||
on { getNetworkStatus() } doReturn Property("test", "mocked")
|
on { getNetworkStatus() } doReturn Property("test", "mocked")
|
||||||
})
|
}
|
||||||
.nodeRegistry(mock())
|
nodeRegistry = mock()
|
||||||
.labeler(spy(DefaultLabeler()))
|
labeler = spy(DefaultLabeler())
|
||||||
.powRepo(testPowRepo)
|
proofOfWorkRepo = testPowRepo
|
||||||
.proofOfWorkEngine(testPowEngine)
|
proofOfWorkEngine = testPowEngine
|
||||||
.build()
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
TTL.msg = 2 * MINUTE
|
TTL.msg = 2 * MINUTE
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@BeforeEach
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
testPowRepo.reset()
|
testPowRepo.reset()
|
||||||
}
|
}
|
||||||
@ -125,7 +128,7 @@ class BitmessageContextTest {
|
|||||||
ctx.addContact(contact)
|
ctx.addContact(contact)
|
||||||
|
|
||||||
verify(ctx.addresses, timeout(1000).atLeastOnce()).save(eq(contact))
|
verify(ctx.addresses, timeout(1000).atLeastOnce()).save(eq(contact))
|
||||||
verify(testPowEngine, timeout(1000)).calculateNonce(any(), any(), any())
|
verify(testPowEngine, timeout(1000)).calculateNonce(any(), any(), any<ProofOfWorkEngine.Callback>())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -138,12 +141,12 @@ class BitmessageContextTest {
|
|||||||
ctx.addContact(contact)
|
ctx.addContact(contact)
|
||||||
|
|
||||||
verify(ctx.addresses, times(1)).save(contact)
|
verify(ctx.addresses, times(1)).save(contact)
|
||||||
verify(testPowEngine, never()).calculateNonce(any(), any(), any())
|
verify(testPowEngine, never()).calculateNonce(any(), any(), any<ProofOfWorkEngine.Callback>())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `ensure V2Pubkey is not requested if it exists in inventory`() {
|
fun `ensure V2Pubkey is not requested if it exists in inventory`() {
|
||||||
inventory.init(
|
testInventory.init(
|
||||||
"V1Msg.payload",
|
"V1Msg.payload",
|
||||||
"V2GetPubkey.payload",
|
"V2GetPubkey.payload",
|
||||||
"V2Pubkey.payload",
|
"V2Pubkey.payload",
|
||||||
@ -161,12 +164,12 @@ class BitmessageContextTest {
|
|||||||
ctx.addContact(contact)
|
ctx.addContact(contact)
|
||||||
|
|
||||||
verify(ctx.addresses, atLeastOnce()).save(contact)
|
verify(ctx.addresses, atLeastOnce()).save(contact)
|
||||||
verify(testPowEngine, never()).calculateNonce(any(), any(), any())
|
verify(testPowEngine, never()).calculateNonce(any(), any(), any<ProofOfWorkEngine.Callback>())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `ensure V4Pubkey is not requested if it exists in inventory`() {
|
fun `ensure V4Pubkey is not requested if it exists in inventory`() {
|
||||||
inventory.init(
|
testInventory.init(
|
||||||
"V1Msg.payload",
|
"V1Msg.payload",
|
||||||
"V2GetPubkey.payload",
|
"V2GetPubkey.payload",
|
||||||
"V2Pubkey.payload",
|
"V2Pubkey.payload",
|
||||||
@ -185,14 +188,14 @@ class BitmessageContextTest {
|
|||||||
ctx.addContact(contact)
|
ctx.addContact(contact)
|
||||||
|
|
||||||
verify(ctx.addresses, atLeastOnce()).save(any())
|
verify(ctx.addresses, atLeastOnce()).save(any())
|
||||||
verify(testPowEngine, never()).calculateNonce(any(), any(), any())
|
verify(testPowEngine, never()).calculateNonce(any(), any(), any<ProofOfWorkEngine.Callback>())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `ensure subscription is added and existing broadcasts retrieved`() {
|
fun `ensure subscription is added and existing broadcasts retrieved`() {
|
||||||
val address = BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ")
|
val address = BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ")
|
||||||
|
|
||||||
inventory.init(
|
testInventory.init(
|
||||||
"V4Broadcast.payload",
|
"V4Broadcast.payload",
|
||||||
"V5Broadcast.payload"
|
"V5Broadcast.payload"
|
||||||
)
|
)
|
||||||
@ -201,61 +204,74 @@ class BitmessageContextTest {
|
|||||||
ctx.addSubscribtion(address)
|
ctx.addSubscribtion(address)
|
||||||
|
|
||||||
verify(ctx.addresses, atLeastOnce()).save(address)
|
verify(ctx.addresses, atLeastOnce()).save(address)
|
||||||
assertThat(address.isSubscribed, `is`(true))
|
assertTrue(address.isSubscribed)
|
||||||
verify(ctx.internals.inventory).getObjects(eq(address.stream), any(), any())
|
verify(ctx.internals.inventory).getObjects(eq(address.stream), any(), any())
|
||||||
verify(listener).receive(any())
|
verify(testListener).receive(any())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `ensure identity is created`() {
|
fun `ensure identity is created`() {
|
||||||
assertThat(ctx.createIdentity(false), notNullValue())
|
assertNotNull(ctx.createIdentity(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `ensure message is sent`() {
|
fun `ensure message is sent`() {
|
||||||
ctx.send(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), TestUtils.loadContact(),
|
ctx.send(
|
||||||
"Subject", "Message")
|
TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), TestUtils.loadContact(),
|
||||||
|
"Subject", "Message"
|
||||||
|
)
|
||||||
verify(ctx.internals.proofOfWorkRepository, timeout(10000)).putObject(
|
verify(ctx.internals.proofOfWorkRepository, timeout(10000)).putObject(
|
||||||
argThat { payload.type == ObjectType.MSG }, eq(1000L), eq(1000L))
|
argThat { payload.type == ObjectType.MSG }, eq(1000L), eq(1000L)
|
||||||
|
)
|
||||||
assertEquals(2, testPowRepo.added)
|
assertEquals(2, testPowRepo.added)
|
||||||
verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat<Plaintext> { type == Type.MSG })
|
verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat<Plaintext> { type == Type.MSG })
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `ensure pubkey is requested if it is missing`() {
|
fun `ensure pubkey is requested if it is missing`() {
|
||||||
ctx.send(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"),
|
ctx.send(
|
||||||
|
TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"),
|
||||||
BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"),
|
BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"),
|
||||||
"Subject", "Message")
|
"Subject", "Message"
|
||||||
|
)
|
||||||
verify(testPowRepo, timeout(10000).atLeastOnce())
|
verify(testPowRepo, timeout(10000).atLeastOnce())
|
||||||
.putObject(argThat { payload.type == ObjectType.GET_PUBKEY }, eq(1000L), eq(1000L))
|
.putObject(argThat { payload.type == ObjectType.GET_PUBKEY }, eq(1000L), eq(1000L))
|
||||||
verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat<Plaintext> { type == Type.MSG })
|
verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat<Plaintext> { type == Type.MSG })
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException::class)
|
@Test
|
||||||
fun `ensure sender must be identity`() {
|
fun `ensure sender must be identity`() {
|
||||||
ctx.send(BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"),
|
assertThrows(IllegalArgumentException::class.java) {
|
||||||
|
ctx.send(
|
||||||
BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"),
|
BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"),
|
||||||
"Subject", "Message")
|
BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"),
|
||||||
|
"Subject", "Message"
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `ensure broadcast is sent`() {
|
fun `ensure broadcast is sent`() {
|
||||||
ctx.broadcast(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"),
|
ctx.broadcast(
|
||||||
"Subject", "Message")
|
TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"),
|
||||||
|
"Subject", "Message"
|
||||||
|
)
|
||||||
verify(ctx.internals.proofOfWorkRepository, timeout(1000).atLeastOnce())
|
verify(ctx.internals.proofOfWorkRepository, timeout(1000).atLeastOnce())
|
||||||
.putObject(argThat { payload.type == ObjectType.BROADCAST }, eq(1000L), eq(1000L))
|
.putObject(argThat { payload.type == ObjectType.BROADCAST }, eq(1000L), eq(1000L))
|
||||||
verify(testPowEngine).calculateNonce(any(), any(), any())
|
verify(testPowEngine).calculateNonce(any(), any(), any<ProofOfWorkEngine.Callback>())
|
||||||
verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat<Plaintext> { type == Type.BROADCAST })
|
verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat<Plaintext> { type == Type.BROADCAST })
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException::class)
|
@Test
|
||||||
fun `ensure sender without private key throws exception`() {
|
fun `ensure sender without private key throws exception`() {
|
||||||
|
assertThrows(IllegalArgumentException::class.java) {
|
||||||
val msg = Plaintext.Builder(Type.BROADCAST)
|
val msg = Plaintext.Builder(Type.BROADCAST)
|
||||||
.from(BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
|
.from(BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
|
||||||
.message("Subject", "Message")
|
.message("Subject", "Message")
|
||||||
.build()
|
.build()
|
||||||
ctx.send(msg)
|
ctx.send(msg)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `ensure chan is joined`() {
|
fun `ensure chan is joined`() {
|
||||||
@ -325,6 +341,6 @@ class BitmessageContextTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `ensure status contains user agent`() {
|
fun `ensure status contains user agent`() {
|
||||||
val userAgent = ctx.status().getProperty("user agent")?.value.toString()
|
val userAgent = ctx.status().getProperty("user agent")?.value.toString()
|
||||||
assertThat(userAgent, `is`("/Jabit:${BitmessageContext.version}/"))
|
assertEquals("/Jabit:${BitmessageContext.version}/", userAgent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,9 +21,9 @@ import ch.dissem.bitmessage.entity.payload.V4Broadcast
|
|||||||
import ch.dissem.bitmessage.entity.payload.V5Broadcast
|
import ch.dissem.bitmessage.entity.payload.V5Broadcast
|
||||||
import ch.dissem.bitmessage.utils.TestBase
|
import ch.dissem.bitmessage.utils.TestBase
|
||||||
import ch.dissem.bitmessage.utils.TestUtils
|
import ch.dissem.bitmessage.utils.TestUtils
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
class DecryptionTest : TestBase() {
|
class DecryptionTest : TestBase() {
|
||||||
@Test
|
@Test
|
||||||
|
@ -34,8 +34,8 @@ import ch.dissem.bitmessage.utils.TestUtils
|
|||||||
import ch.dissem.bitmessage.utils.UnixTime.MINUTE
|
import ch.dissem.bitmessage.utils.UnixTime.MINUTE
|
||||||
import ch.dissem.bitmessage.utils.UnixTime.now
|
import ch.dissem.bitmessage.utils.UnixTime.now
|
||||||
import com.nhaarman.mockito_kotlin.*
|
import com.nhaarman.mockito_kotlin.*
|
||||||
import org.junit.Before
|
import org.junit.jupiter.api.BeforeAll
|
||||||
import org.junit.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Christian Basler
|
* @author Christian Basler
|
||||||
@ -47,7 +47,7 @@ class DefaultMessageListenerTest : TestBase() {
|
|||||||
cryptography = BouncyCryptography()
|
cryptography = BouncyCryptography()
|
||||||
)
|
)
|
||||||
|
|
||||||
@Before
|
@BeforeAll
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
listener = ctx.networkListener as DefaultMessageListener
|
listener = ctx.networkListener as DefaultMessageListener
|
||||||
}
|
}
|
||||||
|
@ -24,9 +24,9 @@ import ch.dissem.bitmessage.entity.valueobject.PrivateKey
|
|||||||
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||||
import ch.dissem.bitmessage.utils.TestBase
|
import ch.dissem.bitmessage.utils.TestBase
|
||||||
import ch.dissem.bitmessage.utils.TestUtils
|
import ch.dissem.bitmessage.utils.TestUtils
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.Assert.assertNotNull
|
import org.junit.jupiter.api.Assertions.assertNotNull
|
||||||
import org.junit.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
class EncryptionTest : TestBase() {
|
class EncryptionTest : TestBase() {
|
||||||
@Test
|
@Test
|
||||||
|
@ -23,14 +23,14 @@ import ch.dissem.bitmessage.entity.Plaintext.Type.MSG
|
|||||||
import ch.dissem.bitmessage.entity.payload.GenericPayload
|
import ch.dissem.bitmessage.entity.payload.GenericPayload
|
||||||
import ch.dissem.bitmessage.entity.payload.Msg
|
import ch.dissem.bitmessage.entity.payload.Msg
|
||||||
import ch.dissem.bitmessage.ports.Cryptography
|
import ch.dissem.bitmessage.ports.Cryptography
|
||||||
|
import ch.dissem.bitmessage.ports.ProofOfWorkEngine
|
||||||
import ch.dissem.bitmessage.ports.ProofOfWorkRepository
|
import ch.dissem.bitmessage.ports.ProofOfWorkRepository
|
||||||
import ch.dissem.bitmessage.utils.Singleton
|
import ch.dissem.bitmessage.utils.Singleton
|
||||||
import ch.dissem.bitmessage.utils.TestUtils
|
import ch.dissem.bitmessage.utils.TestUtils
|
||||||
import com.nhaarman.mockito_kotlin.*
|
import com.nhaarman.mockito_kotlin.*
|
||||||
import org.hamcrest.CoreMatchers.equalTo
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.Assert.assertThat
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.Before
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.Test
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.properties.Delegates
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ class ProofOfWorkServiceTest {
|
|||||||
|
|
||||||
private var obj by Delegates.notNull<ObjectMessage>()
|
private var obj by Delegates.notNull<ObjectMessage>()
|
||||||
|
|
||||||
@Before
|
@BeforeEach
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
cryptography = spy(BouncyCryptography())
|
cryptography = spy(BouncyCryptography())
|
||||||
Singleton.initialize(cryptography)
|
Singleton.initialize(cryptography)
|
||||||
@ -65,11 +65,11 @@ class ProofOfWorkServiceTest {
|
|||||||
fun `ensure missing proof of work is done`() {
|
fun `ensure missing proof of work is done`() {
|
||||||
whenever(ctx.proofOfWorkRepository.getItems()).thenReturn(Arrays.asList<ByteArray>(ByteArray(64)))
|
whenever(ctx.proofOfWorkRepository.getItems()).thenReturn(Arrays.asList<ByteArray>(ByteArray(64)))
|
||||||
whenever(ctx.proofOfWorkRepository.getItem(any())).thenReturn(ProofOfWorkRepository.Item(obj, 1001, 1002))
|
whenever(ctx.proofOfWorkRepository.getItem(any())).thenReturn(ProofOfWorkRepository.Item(obj, 1001, 1002))
|
||||||
doNothing().whenever(cryptography).doProofOfWork(any(), any(), any(), any())
|
doNothing().whenever(cryptography).doProofOfWork(any(), any(), any(), any<ProofOfWorkEngine.Callback>())
|
||||||
|
|
||||||
ctx.proofOfWorkService.doMissingProofOfWork(10)
|
ctx.proofOfWorkService.doMissingProofOfWork(10)
|
||||||
|
|
||||||
verify(cryptography, timeout(1000)).doProofOfWork(eq(obj), eq(1001L), eq(1002L), any())
|
verify(cryptography, timeout(1000)).doProofOfWork(eq(obj), eq(1001L), eq(1002L), any<ProofOfWorkEngine.Callback>())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -95,6 +95,6 @@ class ProofOfWorkServiceTest {
|
|||||||
verify(ctx.proofOfWorkRepository).removeObject(eq(initialHash))
|
verify(ctx.proofOfWorkRepository).removeObject(eq(initialHash))
|
||||||
verify(ctx.inventory).storeObject(eq(objectMessage))
|
verify(ctx.inventory).storeObject(eq(objectMessage))
|
||||||
verify(ctx.networkHandler).offer(eq(objectMessage.inventoryVector))
|
verify(ctx.networkHandler).offer(eq(objectMessage.inventoryVector))
|
||||||
assertThat(plaintext.inventoryVector, equalTo(objectMessage.inventoryVector))
|
assertEquals(objectMessage.inventoryVector, plaintext.inventoryVector)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,8 +23,8 @@ import ch.dissem.bitmessage.entity.payload.Pubkey
|
|||||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
|
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
|
||||||
import ch.dissem.bitmessage.utils.TestBase
|
import ch.dissem.bitmessage.utils.TestBase
|
||||||
import ch.dissem.bitmessage.utils.TestUtils
|
import ch.dissem.bitmessage.utils.TestUtils
|
||||||
import org.junit.Assert.*
|
import org.junit.jupiter.api.Assertions.*
|
||||||
import org.junit.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
class SignatureTest : TestBase() {
|
class SignatureTest : TestBase() {
|
||||||
@Test
|
@Test
|
||||||
|
@ -22,16 +22,15 @@ import ch.dissem.bitmessage.entity.payload.Pubkey.Feature.INCLUDE_DESTINATION
|
|||||||
import ch.dissem.bitmessage.entity.payload.V4Pubkey
|
import ch.dissem.bitmessage.entity.payload.V4Pubkey
|
||||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
|
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
|
||||||
import ch.dissem.bitmessage.utils.*
|
import ch.dissem.bitmessage.utils.*
|
||||||
import org.junit.Assert
|
import org.junit.jupiter.api.Assertions.*
|
||||||
import org.junit.Assert.*
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.Test
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class BitmessageAddressTest : TestBase() {
|
class BitmessageAddressTest : TestBase() {
|
||||||
@Test
|
@Test
|
||||||
fun `ensure feature flag is calculated correctly`() {
|
fun `ensure feature flag is calculated correctly`() {
|
||||||
Assert.assertEquals(1, Pubkey.Feature.bitfield(DOES_ACK))
|
assertEquals(1, Pubkey.Feature.bitfield(DOES_ACK))
|
||||||
assertEquals(2, Pubkey.Feature.bitfield(INCLUDE_DESTINATION))
|
assertEquals(2, Pubkey.Feature.bitfield(INCLUDE_DESTINATION))
|
||||||
assertEquals(3, Pubkey.Feature.bitfield(DOES_ACK, INCLUDE_DESTINATION))
|
assertEquals(3, Pubkey.Feature.bitfield(DOES_ACK, INCLUDE_DESTINATION))
|
||||||
}
|
}
|
||||||
@ -74,7 +73,7 @@ class BitmessageAddressTest : TestBase() {
|
|||||||
try {
|
try {
|
||||||
address.pubkey = pubkey
|
address.pubkey = pubkey
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
fail(e.message)
|
fail<Unit>(e.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -82,7 +81,7 @@ class BitmessageAddressTest : TestBase() {
|
|||||||
@Test
|
@Test
|
||||||
fun `ensure V3Pubkey can be imported`() {
|
fun `ensure V3Pubkey can be imported`() {
|
||||||
val address = BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ")
|
val address = BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ")
|
||||||
Assert.assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), address.ripe)
|
assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), address.ripe)
|
||||||
|
|
||||||
val objectMessage = TestUtils.loadObjectMessage(3, "V3Pubkey.payload")
|
val objectMessage = TestUtils.loadObjectMessage(3, "V3Pubkey.payload")
|
||||||
val pubkey = objectMessage.payload as Pubkey
|
val pubkey = objectMessage.payload as Pubkey
|
||||||
@ -90,7 +89,7 @@ class BitmessageAddressTest : TestBase() {
|
|||||||
try {
|
try {
|
||||||
address.pubkey = pubkey
|
address.pubkey = pubkey
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
fail(e.message)
|
fail<Unit>(e.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), pubkey.ripe)
|
assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), pubkey.ripe)
|
||||||
@ -107,7 +106,7 @@ class BitmessageAddressTest : TestBase() {
|
|||||||
try {
|
try {
|
||||||
address.pubkey = pubkey
|
address.pubkey = pubkey
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
fail(e.message)
|
fail<Unit>(e.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
assertTrue(address.has(DOES_ACK))
|
assertTrue(address.has(DOES_ACK))
|
||||||
|
File diff suppressed because one or more lines are too long
@ -24,9 +24,8 @@ import ch.dissem.bitmessage.entity.valueobject.extended.Message
|
|||||||
import ch.dissem.bitmessage.factory.Factory
|
import ch.dissem.bitmessage.factory.Factory
|
||||||
import ch.dissem.bitmessage.utils.TestBase
|
import ch.dissem.bitmessage.utils.TestBase
|
||||||
import ch.dissem.bitmessage.utils.TestUtils
|
import ch.dissem.bitmessage.utils.TestUtils
|
||||||
import org.hamcrest.Matchers.`is`
|
import org.junit.jupiter.api.Assertions.*
|
||||||
import org.junit.Assert.*
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.Test
|
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.ObjectInputStream
|
import java.io.ObjectInputStream
|
||||||
@ -86,16 +85,16 @@ class SerializationTest : TestBase() {
|
|||||||
.signature(ByteArray(0))
|
.signature(ByteArray(0))
|
||||||
.build()
|
.build()
|
||||||
val out = ByteArrayOutputStream()
|
val out = ByteArrayOutputStream()
|
||||||
expected.write(out)
|
expected.writer().write(out)
|
||||||
val `in` = ByteArrayInputStream(out.toByteArray())
|
val input = ByteArrayInputStream(out.toByteArray())
|
||||||
val actual = Plaintext.read(MSG, `in`)
|
val actual = Plaintext.read(MSG, input)
|
||||||
|
|
||||||
// Received is automatically set on deserialization, so we'll need to set it to null
|
// Received is automatically set on deserialization, so we'll need to set it to null
|
||||||
val received = Plaintext::class.java.getDeclaredField("received")
|
val received = Plaintext::class.java.getDeclaredField("received")
|
||||||
received.isAccessible = true
|
received.isAccessible = true
|
||||||
received.set(actual, null)
|
received.set(actual, null)
|
||||||
|
|
||||||
assertThat(expected, `is`(actual))
|
assertEquals(actual, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -103,17 +102,46 @@ class SerializationTest : TestBase() {
|
|||||||
val expected = Plaintext.Builder(MSG)
|
val expected = Plaintext.Builder(MSG)
|
||||||
.from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
|
.from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
|
||||||
.to(TestUtils.loadContact())
|
.to(TestUtils.loadContact())
|
||||||
.message(Message.Builder()
|
.message(
|
||||||
|
Message.Builder()
|
||||||
.subject("Subject")
|
.subject("Subject")
|
||||||
.body("Message")
|
.body("Message")
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
.ackData("ackMessage".toByteArray())
|
.ackData("ackMessage".toByteArray())
|
||||||
.signature(ByteArray(0))
|
.signature(ByteArray(0))
|
||||||
.build()
|
.build()
|
||||||
val out = ByteArrayOutputStream()
|
val out = ByteArrayOutputStream()
|
||||||
expected.write(out)
|
expected.writer().write(out)
|
||||||
val `in` = ByteArrayInputStream(out.toByteArray())
|
val input = ByteArrayInputStream(out.toByteArray())
|
||||||
val actual = Plaintext.read(MSG, `in`)
|
val actual = Plaintext.read(MSG, input)
|
||||||
|
|
||||||
|
// Received is automatically set on deserialization, so we'll need to set it to null
|
||||||
|
val received = Plaintext::class.java.getDeclaredField("received")
|
||||||
|
received.isAccessible = true
|
||||||
|
received.set(actual, null)
|
||||||
|
|
||||||
|
assertEquals(expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure plaintext without recipient can be serialized (needed for saving drafts)`() {
|
||||||
|
val expected = Plaintext.Builder(MSG)
|
||||||
|
.from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
|
||||||
|
.message(
|
||||||
|
Message.Builder()
|
||||||
|
.subject("Subject")
|
||||||
|
.body("Message")
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.signature(ByteArray(0))
|
||||||
|
.status(Plaintext.Status.DRAFT)
|
||||||
|
.build()
|
||||||
|
val out = ByteArrayOutputStream()
|
||||||
|
expected.writer().write(out)
|
||||||
|
val input = ByteArrayInputStream(out.toByteArray())
|
||||||
|
val actual = Plaintext.read(MSG, input)
|
||||||
|
actual.status = Plaintext.Status.DRAFT // status isn't serialized, that's OK
|
||||||
|
|
||||||
// Received is automatically set on deserialization, so we'll need to set it to null
|
// Received is automatically set on deserialization, so we'll need to set it to null
|
||||||
val received = Plaintext::class.java.getDeclaredField("received")
|
val received = Plaintext::class.java.getDeclaredField("received")
|
||||||
@ -136,9 +164,9 @@ class SerializationTest : TestBase() {
|
|||||||
assertNotNull(ackMessage1)
|
assertNotNull(ackMessage1)
|
||||||
|
|
||||||
val out = ByteArrayOutputStream()
|
val out = ByteArrayOutputStream()
|
||||||
expected.write(out)
|
expected.writer().write(out)
|
||||||
val `in` = ByteArrayInputStream(out.toByteArray())
|
val input = ByteArrayInputStream(out.toByteArray())
|
||||||
val actual = Plaintext.read(MSG, `in`)
|
val actual = Plaintext.read(MSG, input)
|
||||||
|
|
||||||
// Received is automatically set on deserialization, so we'll need to set it to null
|
// Received is automatically set on deserialization, so we'll need to set it to null
|
||||||
val received = Plaintext::class.java.getDeclaredField("received")
|
val received = Plaintext::class.java.getDeclaredField("received")
|
||||||
@ -159,7 +187,7 @@ class SerializationTest : TestBase() {
|
|||||||
val inv = Inv(ivs)
|
val inv = Inv(ivs)
|
||||||
val before = NetworkMessage(inv)
|
val before = NetworkMessage(inv)
|
||||||
val out = ByteArrayOutputStream()
|
val out = ByteArrayOutputStream()
|
||||||
before.write(out)
|
before.writer().write(out)
|
||||||
|
|
||||||
val after = Factory.getNetworkMessage(3, ByteArrayInputStream(out.toByteArray()))
|
val after = Factory.getNetworkMessage(3, ByteArrayInputStream(out.toByteArray()))
|
||||||
assertNotNull(after)
|
assertNotNull(after)
|
||||||
@ -169,11 +197,11 @@ class SerializationTest : TestBase() {
|
|||||||
|
|
||||||
private fun doTest(resourceName: String, version: Int, expectedPayloadType: Class<*>) {
|
private fun doTest(resourceName: String, version: Int, expectedPayloadType: Class<*>) {
|
||||||
val data = TestUtils.getBytes(resourceName)
|
val data = TestUtils.getBytes(resourceName)
|
||||||
val `in` = ByteArrayInputStream(data)
|
val input = ByteArrayInputStream(data)
|
||||||
val objectMessage = Factory.getObjectMessage(version, `in`, data.size)
|
val objectMessage = Factory.getObjectMessage(version, input, data.size)
|
||||||
val out = ByteArrayOutputStream()
|
val out = ByteArrayOutputStream()
|
||||||
assertNotNull(objectMessage)
|
assertNotNull(objectMessage)
|
||||||
objectMessage!!.write(out)
|
objectMessage!!.writer().write(out)
|
||||||
assertArrayEquals(data, out.toByteArray())
|
assertArrayEquals(data, out.toByteArray())
|
||||||
assertEquals(expectedPayloadType.canonicalName, objectMessage.payload.javaClass.canonicalName)
|
assertEquals(expectedPayloadType.canonicalName, objectMessage.payload.javaClass.canonicalName)
|
||||||
}
|
}
|
||||||
@ -190,8 +218,8 @@ class SerializationTest : TestBase() {
|
|||||||
val oos = ObjectOutputStream(out)
|
val oos = ObjectOutputStream(out)
|
||||||
oos.writeObject(plaintext)
|
oos.writeObject(plaintext)
|
||||||
|
|
||||||
val `in` = ByteArrayInputStream(out.toByteArray())
|
val input = ByteArrayInputStream(out.toByteArray())
|
||||||
val ois = ObjectInputStream(`in`)
|
val ois = ObjectInputStream(input)
|
||||||
assertEquals(plaintext, ois.readObject())
|
assertEquals(plaintext, ois.readObject())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,19 +20,25 @@ import ch.dissem.bitmessage.utils.Bytes
|
|||||||
import ch.dissem.bitmessage.utils.CallbackWaiter
|
import ch.dissem.bitmessage.utils.CallbackWaiter
|
||||||
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||||
import ch.dissem.bitmessage.utils.TestBase
|
import ch.dissem.bitmessage.utils.TestBase
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTimeoutPreemptively
|
||||||
import org.junit.Test
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import java.time.Duration.ofSeconds
|
||||||
|
|
||||||
class ProofOfWorkEngineTest : TestBase() {
|
class ProofOfWorkEngineTest : TestBase() {
|
||||||
@Test(timeout = 90000)
|
@Test
|
||||||
fun `test SimplePOWEngine`() {
|
fun `test SimplePOWEngine`() {
|
||||||
|
assertTimeoutPreemptively(ofSeconds(90)) {
|
||||||
testPOW(SimplePOWEngine())
|
testPOW(SimplePOWEngine())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test(timeout = 90000)
|
@Test
|
||||||
fun `test MultiThreadedPOWEngine`() {
|
fun `test MultiThreadedPOWEngine`() {
|
||||||
|
assertTimeoutPreemptively(ofSeconds(90)) {
|
||||||
testPOW(MultiThreadedPOWEngine())
|
testPOW(MultiThreadedPOWEngine())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun testPOW(engine: ProofOfWorkEngine) {
|
private fun testPOW(engine: ProofOfWorkEngine) {
|
||||||
val initialHash = cryptography().sha512(byteArrayOf(1, 3, 6, 4))
|
val initialHash = cryptography().sha512(byteArrayOf(1, 3, 6, 4))
|
||||||
@ -65,6 +71,6 @@ class ProofOfWorkEngineTest : TestBase() {
|
|||||||
val nonce2 = waiter2.waitForValue()!!
|
val nonce2 = waiter2.waitForValue()!!
|
||||||
println("Calculating nonce1 took ${waiter2.time}ms")
|
println("Calculating nonce1 took ${waiter2.time}ms")
|
||||||
assertTrue(Bytes.lt(cryptography().doubleSha512(nonce2, initialHash2), target2, 8))
|
assertTrue(Bytes.lt(cryptography().doubleSha512(nonce2, initialHash2), target2, 8))
|
||||||
assertTrue("Second nonce1 must be quicker to find", waiter1.time > waiter2.time)
|
assertTrue(waiter1.time > waiter2.time, "Second nonce1 must be quicker to find")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,8 @@
|
|||||||
package ch.dissem.bitmessage.utils
|
package ch.dissem.bitmessage.utils
|
||||||
|
|
||||||
import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography
|
import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography
|
||||||
import org.hamcrest.Matchers.`is`
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.Assert.assertThat
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
class Base64Test {
|
class Base64Test {
|
||||||
@Test
|
@Test
|
||||||
@ -29,7 +28,7 @@ class Base64Test {
|
|||||||
val data = cryptography.randomBytes(i)
|
val data = cryptography.randomBytes(i)
|
||||||
val string = Base64.encodeToString(data)
|
val string = Base64.encodeToString(data)
|
||||||
val decoded = Base64.decode(string)
|
val decoded = Base64.decode(string)
|
||||||
assertThat(decoded, `is`(data))
|
assertEquals(data, decoded)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,10 +16,10 @@
|
|||||||
|
|
||||||
package ch.dissem.bitmessage.utils
|
package ch.dissem.bitmessage.utils
|
||||||
|
|
||||||
import org.junit.Assert.assertArrayEquals
|
import org.junit.jupiter.api.Assertions.assertArrayEquals
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.Ignore
|
import org.junit.jupiter.api.Disabled
|
||||||
import org.junit.Test
|
import org.junit.jupiter.api.Test
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@ -46,7 +46,11 @@ class BytesTest {
|
|||||||
for (i in 1..255) {
|
for (i in 1..255) {
|
||||||
val bytes = byteArrayOf(0, v.toByte())
|
val bytes = byteArrayOf(0, v.toByte())
|
||||||
Bytes.inc(bytes, i.toByte())
|
Bytes.inc(bytes, i.toByte())
|
||||||
assertArrayEquals("value = " + v + "; inc = " + i + "; expected = " + (v + i), TestUtils.int16(v + i), bytes)
|
assertArrayEquals(
|
||||||
|
TestUtils.int16(v + i),
|
||||||
|
bytes,
|
||||||
|
"value = " + v + "; inc = " + i + "; expected = " + (v + i)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -55,7 +59,7 @@ class BytesTest {
|
|||||||
* This test is used to compare different implementations of the single byte lt comparison. It an safely be ignored.
|
* This test is used to compare different implementations of the single byte lt comparison. It an safely be ignored.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@Ignore
|
@Disabled
|
||||||
fun `test lower than single byte`() {
|
fun `test lower than single byte`() {
|
||||||
val a = ByteArray(1)
|
val a = ByteArray(1)
|
||||||
val b = ByteArray(1)
|
val b = ByteArray(1)
|
||||||
@ -85,10 +89,13 @@ class BytesTest {
|
|||||||
val a = BigInteger.valueOf(rnd.nextLong()).pow(rnd.nextInt(5) + 1).abs()
|
val a = BigInteger.valueOf(rnd.nextLong()).pow(rnd.nextInt(5) + 1).abs()
|
||||||
val b = BigInteger.valueOf(rnd.nextLong()).pow(rnd.nextInt(5) + 1).abs()
|
val b = BigInteger.valueOf(rnd.nextLong()).pow(rnd.nextInt(5) + 1).abs()
|
||||||
println("a = " + a.toString(16) + "\tb = " + b.toString(16))
|
println("a = " + a.toString(16) + "\tb = " + b.toString(16))
|
||||||
assertEquals(a.compareTo(b) == -1, Bytes.lt(
|
assertEquals(
|
||||||
|
a.compareTo(b) == -1, Bytes.lt(
|
||||||
Bytes.expand(a.toByteArray(), 100),
|
Bytes.expand(a.toByteArray(), 100),
|
||||||
Bytes.expand(b.toByteArray(), 100),
|
Bytes.expand(b.toByteArray(), 100),
|
||||||
100))
|
100
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,17 +16,12 @@
|
|||||||
|
|
||||||
package ch.dissem.bitmessage.utils
|
package ch.dissem.bitmessage.utils
|
||||||
|
|
||||||
import org.junit.Test
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
import java.util.LinkedList
|
|
||||||
|
|
||||||
import org.junit.Assert.assertEquals
|
|
||||||
|
|
||||||
class CollectionsTest {
|
class CollectionsTest {
|
||||||
@Test
|
@Test
|
||||||
fun `ensure select random returns maximum possible items`() {
|
fun `ensure select random returns maximum possible items`() {
|
||||||
val list = LinkedList<Int>()
|
assertEquals(9, Collections.selectRandom(9, listOf(0..9)).size)
|
||||||
list += 0..9
|
|
||||||
assertEquals(9, Collections.selectRandom(9, list).size)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,9 +24,8 @@ import ch.dissem.bitmessage.entity.valueobject.extended.Message
|
|||||||
import ch.dissem.bitmessage.ports.MessageRepository
|
import ch.dissem.bitmessage.ports.MessageRepository
|
||||||
import ch.dissem.bitmessage.utils.TestUtils.RANDOM
|
import ch.dissem.bitmessage.utils.TestUtils.RANDOM
|
||||||
import com.nhaarman.mockito_kotlin.*
|
import com.nhaarman.mockito_kotlin.*
|
||||||
import org.hamcrest.Matchers.`is`
|
import org.junit.jupiter.api.Assertions
|
||||||
import org.junit.Assert.assertThat
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.Test
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class ConversationServiceTest : TestBase() {
|
class ConversationServiceTest : TestBase() {
|
||||||
@ -45,7 +44,7 @@ class ConversationServiceTest : TestBase() {
|
|||||||
|
|
||||||
doReturn(expected).whenever(conversationService).getConversation(any<UUID>())
|
doReturn(expected).whenever(conversationService).getConversation(any<UUID>())
|
||||||
val actual = conversationService.getConversation(UUID.randomUUID())
|
val actual = conversationService.getConversation(UUID.randomUUID())
|
||||||
assertThat(actual, `is`(expected))
|
Assertions.assertEquals(expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -53,57 +52,69 @@ class ConversationServiceTest : TestBase() {
|
|||||||
fun conversation(alice: BitmessageAddress, bob: BitmessageAddress): List<Plaintext> {
|
fun conversation(alice: BitmessageAddress, bob: BitmessageAddress): List<Plaintext> {
|
||||||
val result = LinkedList<Plaintext>()
|
val result = LinkedList<Plaintext>()
|
||||||
|
|
||||||
val older = plaintext(alice, bob,
|
val older = plaintext(
|
||||||
|
alice, bob,
|
||||||
Message.Builder()
|
Message.Builder()
|
||||||
.subject("hey there")
|
.subject("hey there")
|
||||||
.body("does it work?")
|
.body("does it work?")
|
||||||
.build(),
|
.build(),
|
||||||
Plaintext.Status.SENT)
|
Plaintext.Status.SENT
|
||||||
|
)
|
||||||
result.add(older)
|
result.add(older)
|
||||||
|
|
||||||
val root = plaintext(alice, bob,
|
val root = plaintext(
|
||||||
|
alice, bob,
|
||||||
Message.Builder()
|
Message.Builder()
|
||||||
.subject("new test")
|
.subject("new test")
|
||||||
.body("There's a new test in town!")
|
.body("There's a new test in town!")
|
||||||
.build(),
|
.build(),
|
||||||
Plaintext.Status.SENT)
|
Plaintext.Status.SENT
|
||||||
|
)
|
||||||
result.add(root)
|
result.add(root)
|
||||||
|
|
||||||
result.add(
|
result.add(
|
||||||
plaintext(bob, alice,
|
plaintext(
|
||||||
|
bob, alice,
|
||||||
Message.Builder()
|
Message.Builder()
|
||||||
.subject("Re: new test (1a)")
|
.subject("Re: new test (1a)")
|
||||||
.body("Nice!")
|
.body("Nice!")
|
||||||
.addParent(root)
|
.addParent(root)
|
||||||
.build(),
|
.build(),
|
||||||
Plaintext.Status.RECEIVED)
|
Plaintext.Status.RECEIVED
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
val latest = plaintext(bob, alice,
|
val latest = plaintext(
|
||||||
|
bob, alice,
|
||||||
Message.Builder()
|
Message.Builder()
|
||||||
.subject("Re: new test (2b)")
|
.subject("Re: new test (2b)")
|
||||||
.body("PS: it did work!")
|
.body("PS: it did work!")
|
||||||
.addParent(root)
|
.addParent(root)
|
||||||
.addParent(older)
|
.addParent(older)
|
||||||
.build(),
|
.build(),
|
||||||
Plaintext.Status.RECEIVED)
|
Plaintext.Status.RECEIVED
|
||||||
|
)
|
||||||
result.add(latest)
|
result.add(latest)
|
||||||
|
|
||||||
result.add(
|
result.add(
|
||||||
plaintext(alice, bob,
|
plaintext(
|
||||||
|
alice, bob,
|
||||||
Message.Builder()
|
Message.Builder()
|
||||||
.subject("Re: new test (2)")
|
.subject("Re: new test (2)")
|
||||||
.body("")
|
.body("")
|
||||||
.addParent(latest)
|
.addParent(latest)
|
||||||
.build(),
|
.build(),
|
||||||
Plaintext.Status.DRAFT)
|
Plaintext.Status.DRAFT
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
fun plaintext(from: BitmessageAddress, to: BitmessageAddress,
|
fun plaintext(
|
||||||
content: ExtendedEncoding, status: Plaintext.Status): Plaintext {
|
from: BitmessageAddress, to: BitmessageAddress,
|
||||||
|
content: ExtendedEncoding, status: Plaintext.Status
|
||||||
|
): Plaintext {
|
||||||
val builder = Plaintext.Builder(MSG)
|
val builder = Plaintext.Builder(MSG)
|
||||||
.IV(TestUtils.randomInventoryVector())
|
.IV(TestUtils.randomInventoryVector())
|
||||||
.from(from)
|
.from(from)
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
package ch.dissem.bitmessage.utils
|
package ch.dissem.bitmessage.utils
|
||||||
|
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.Test
|
import org.junit.jupiter.api.Test
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
|
|
||||||
@ -34,8 +34,8 @@ class DecodeTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun testCodec(number: Long) {
|
private fun testCodec(number: Long) {
|
||||||
val `is` = ByteArrayOutputStream()
|
val out = ByteArrayOutputStream()
|
||||||
Encode.varInt(number, `is`)
|
Encode.varInt(number, out)
|
||||||
assertEquals(number, Decode.varInt(ByteArrayInputStream(`is`.toByteArray())))
|
assertEquals(number, Decode.varInt(ByteArrayInputStream(out.toByteArray())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,9 @@
|
|||||||
|
|
||||||
package ch.dissem.bitmessage.utils
|
package ch.dissem.bitmessage.utils
|
||||||
|
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.Test
|
import org.junit.jupiter.api.Assumptions
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
|
|
||||||
class EncodeTest {
|
class EncodeTest {
|
||||||
@ -111,7 +112,8 @@ class EncodeTest {
|
|||||||
|
|
||||||
|
|
||||||
fun checkBytes(stream: ByteArrayOutputStream, vararg bytes: Int) {
|
fun checkBytes(stream: ByteArrayOutputStream, vararg bytes: Int) {
|
||||||
assertEquals(bytes.size, stream.size())
|
Assumptions.assumeTrue(bytes.size == stream.size())
|
||||||
|
|
||||||
val streamBytes = stream.toByteArray()
|
val streamBytes = stream.toByteArray()
|
||||||
|
|
||||||
for (i in bytes.indices) {
|
for (i in bytes.indices) {
|
||||||
|
@ -16,14 +16,12 @@
|
|||||||
|
|
||||||
package ch.dissem.bitmessage.utils
|
package ch.dissem.bitmessage.utils
|
||||||
|
|
||||||
import org.junit.Test
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.Assert.assertEquals
|
|
||||||
|
|
||||||
class SqlStringsTest {
|
class SqlStringsTest {
|
||||||
@Test
|
@Test
|
||||||
fun `ensure join works with long array`() {
|
fun `ensure join works with long array`() {
|
||||||
val test = longArrayOf(1L, 2L)
|
assertEquals("1, 2", SqlStrings.join(1L, 2L))
|
||||||
assertEquals("1, 2", SqlStrings.join(*test))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,9 +16,8 @@
|
|||||||
|
|
||||||
package ch.dissem.bitmessage.utils
|
package ch.dissem.bitmessage.utils
|
||||||
|
|
||||||
import org.junit.Test
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.Assert.assertEquals
|
|
||||||
|
|
||||||
class StringsTest {
|
class StringsTest {
|
||||||
@Test
|
@Test
|
||||||
|
@ -17,15 +17,16 @@
|
|||||||
package ch.dissem.bitmessage.utils
|
package ch.dissem.bitmessage.utils
|
||||||
|
|
||||||
import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography
|
import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography
|
||||||
import org.junit.BeforeClass
|
import org.junit.jupiter.api.BeforeAll
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Christian Basler
|
* @author Christian Basler
|
||||||
*/
|
*/
|
||||||
open class TestBase {
|
open class TestBase {
|
||||||
companion object {
|
companion object {
|
||||||
@BeforeClass
|
@BeforeAll
|
||||||
@JvmStatic fun setUpClass() {
|
@JvmStatic
|
||||||
|
fun setUpClass() {
|
||||||
Singleton.initialize(BouncyCryptography())
|
Singleton.initialize(BouncyCryptography())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ package ch.dissem.bitmessage.utils
|
|||||||
|
|
||||||
import ch.dissem.bitmessage.BitmessageContext
|
import ch.dissem.bitmessage.BitmessageContext
|
||||||
import ch.dissem.bitmessage.InternalContext
|
import ch.dissem.bitmessage.InternalContext
|
||||||
|
import ch.dissem.bitmessage.Preferences
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
import ch.dissem.bitmessage.entity.ObjectMessage
|
import ch.dissem.bitmessage.entity.ObjectMessage
|
||||||
import ch.dissem.bitmessage.entity.payload.V4Pubkey
|
import ch.dissem.bitmessage.entity.payload.V4Pubkey
|
||||||
@ -28,7 +29,7 @@ import ch.dissem.bitmessage.factory.Factory
|
|||||||
import ch.dissem.bitmessage.ports.*
|
import ch.dissem.bitmessage.ports.*
|
||||||
import com.nhaarman.mockito_kotlin.mock
|
import com.nhaarman.mockito_kotlin.mock
|
||||||
import com.nhaarman.mockito_kotlin.spy
|
import com.nhaarman.mockito_kotlin.spy
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
@ -39,43 +40,50 @@ import kotlin.NoSuchElementException
|
|||||||
* If there's ever a need for this in production code, it should be rewritten to be more efficient.
|
* If there's ever a need for this in production code, it should be rewritten to be more efficient.
|
||||||
*/
|
*/
|
||||||
object TestUtils {
|
object TestUtils {
|
||||||
@JvmField val RANDOM = Random()
|
@JvmField
|
||||||
|
val RANDOM = Random()
|
||||||
|
|
||||||
@JvmStatic fun int16(number: Int): ByteArray {
|
@JvmStatic
|
||||||
|
fun int16(number: Int): ByteArray {
|
||||||
val out = ByteArrayOutputStream()
|
val out = ByteArrayOutputStream()
|
||||||
Encode.int16(number, out)
|
Encode.int16(number, out)
|
||||||
return out.toByteArray()
|
return out.toByteArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic fun loadObjectMessage(version: Int, resourceName: String): ObjectMessage {
|
@JvmStatic
|
||||||
|
fun loadObjectMessage(version: Int, resourceName: String): ObjectMessage {
|
||||||
val data = getBytes(resourceName)
|
val data = getBytes(resourceName)
|
||||||
val `in` = ByteArrayInputStream(data)
|
val input = ByteArrayInputStream(data)
|
||||||
return Factory.getObjectMessage(version, `in`, data.size) ?: throw NoSuchElementException("error loading object message")
|
return Factory.getObjectMessage(version, input, data.size)
|
||||||
|
?: throw NoSuchElementException("error loading object message")
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic fun getBytes(resourceName: String): ByteArray {
|
@JvmStatic
|
||||||
val `in` = javaClass.classLoader.getResourceAsStream(resourceName)
|
fun getBytes(resourceName: String): ByteArray {
|
||||||
|
val input = javaClass.classLoader.getResourceAsStream(resourceName)
|
||||||
val out = ByteArrayOutputStream()
|
val out = ByteArrayOutputStream()
|
||||||
val buffer = ByteArray(1024)
|
val buffer = ByteArray(1024)
|
||||||
var len = `in`.read(buffer)
|
var len = input.read(buffer)
|
||||||
while (len != -1) {
|
while (len != -1) {
|
||||||
out.write(buffer, 0, len)
|
out.write(buffer, 0, len)
|
||||||
len = `in`.read(buffer)
|
len = input.read(buffer)
|
||||||
}
|
}
|
||||||
return out.toByteArray()
|
return out.toByteArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic fun randomInventoryVector(): InventoryVector {
|
@JvmStatic
|
||||||
|
fun randomInventoryVector(): InventoryVector {
|
||||||
val bytes = ByteArray(32)
|
val bytes = ByteArray(32)
|
||||||
RANDOM.nextBytes(bytes)
|
RANDOM.nextBytes(bytes)
|
||||||
return InventoryVector(bytes)
|
return InventoryVector(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic fun getResource(resourceName: String): InputStream {
|
@JvmStatic
|
||||||
return javaClass.classLoader.getResourceAsStream(resourceName)
|
fun getResource(resourceName: String): InputStream =
|
||||||
}
|
javaClass.classLoader.getResourceAsStream(resourceName)
|
||||||
|
|
||||||
@JvmStatic fun loadIdentity(address: String): BitmessageAddress {
|
@JvmStatic
|
||||||
|
fun loadIdentity(address: String): BitmessageAddress {
|
||||||
val privateKey = PrivateKey.read(TestUtils.getResource(address + ".privkey"))
|
val privateKey = PrivateKey.read(TestUtils.getResource(address + ".privkey"))
|
||||||
val identity = BitmessageAddress(privateKey)
|
val identity = BitmessageAddress(privateKey)
|
||||||
assertEquals(address, identity.address)
|
assertEquals(address, identity.address)
|
||||||
@ -83,7 +91,8 @@ object TestUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(DecryptionFailedException::class)
|
@Throws(DecryptionFailedException::class)
|
||||||
@JvmStatic fun loadContact(): BitmessageAddress {
|
@JvmStatic
|
||||||
|
fun loadContact(): BitmessageAddress {
|
||||||
val address = BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h")
|
val address = BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h")
|
||||||
val objectMessage = TestUtils.loadObjectMessage(3, "V4Pubkey.payload")
|
val objectMessage = TestUtils.loadObjectMessage(3, "V4Pubkey.payload")
|
||||||
objectMessage.decrypt(address.publicDecryptionKey)
|
objectMessage.decrypt(address.publicDecryptionKey)
|
||||||
@ -91,18 +100,21 @@ object TestUtils {
|
|||||||
return address
|
return address
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic fun loadPubkey(address: BitmessageAddress) {
|
@JvmStatic
|
||||||
|
fun loadPubkey(address: BitmessageAddress) {
|
||||||
val bytes = getBytes(address.address + ".pubkey")
|
val bytes = getBytes(address.address + ".pubkey")
|
||||||
val pubkey = Factory.readPubkey(address.version, address.stream, ByteArrayInputStream(bytes), bytes.size, false)
|
val pubkey = Factory.readPubkey(address.version, address.stream, ByteArrayInputStream(bytes), bytes.size, false)
|
||||||
address.pubkey = pubkey
|
address.pubkey = pubkey
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic fun mockedInternalContext(
|
@JvmStatic
|
||||||
|
fun mockedInternalContext(
|
||||||
cryptography: Cryptography = mock {},
|
cryptography: Cryptography = mock {},
|
||||||
inventory: Inventory = mock {},
|
inventory: Inventory = mock {},
|
||||||
nodeRegistry: NodeRegistry = mock {},
|
nodeRegistry: NodeRegistry = mock {},
|
||||||
networkHandler: NetworkHandler = mock {},
|
networkHandler: NetworkHandler = mock {},
|
||||||
addressRepository: AddressRepository = mock {},
|
addressRepository: AddressRepository = mock {},
|
||||||
|
labelRepository: LabelRepository = mock {},
|
||||||
messageRepository: MessageRepository = mock {},
|
messageRepository: MessageRepository = mock {},
|
||||||
proofOfWorkRepository: ProofOfWorkRepository = mock {},
|
proofOfWorkRepository: ProofOfWorkRepository = mock {},
|
||||||
proofOfWorkEngine: ProofOfWorkEngine = mock {},
|
proofOfWorkEngine: ProofOfWorkEngine = mock {},
|
||||||
@ -119,16 +131,19 @@ object TestUtils {
|
|||||||
nodeRegistry,
|
nodeRegistry,
|
||||||
networkHandler,
|
networkHandler,
|
||||||
addressRepository,
|
addressRepository,
|
||||||
|
labelRepository,
|
||||||
messageRepository,
|
messageRepository,
|
||||||
proofOfWorkRepository,
|
proofOfWorkRepository,
|
||||||
proofOfWorkEngine,
|
proofOfWorkEngine,
|
||||||
customCommandHandler,
|
customCommandHandler,
|
||||||
listener,
|
listener,
|
||||||
labeler,
|
labeler,
|
||||||
"/Jabit:TEST/",
|
Preferences().apply {
|
||||||
port,
|
this.userAgent = "/Jabit:TEST/"
|
||||||
connectionTTL,
|
this.port = port
|
||||||
connectionLimit
|
this.connectionTTL = connectionTTL
|
||||||
|
this.connectionLimit = connectionLimit
|
||||||
|
}
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,8 @@ uploadArchives {
|
|||||||
dependencies {
|
dependencies {
|
||||||
compile project(':core')
|
compile project(':core')
|
||||||
compile 'org.bouncycastle:bcprov-jdk15on'
|
compile 'org.bouncycastle:bcprov-jdk15on'
|
||||||
testCompile 'junit:junit'
|
|
||||||
testCompile 'com.nhaarman:mockito-kotlin'
|
testCompile 'com.nhaarman:mockito-kotlin'
|
||||||
|
testCompile 'org.junit.jupiter:junit-jupiter-api'
|
||||||
|
testRuntime 'org.junit.jupiter:junit-jupiter-engine'
|
||||||
testCompile project(path: ':core', configuration: 'testArtifacts')
|
testCompile project(path: ':core', configuration: 'testArtifacts')
|
||||||
}
|
}
|
||||||
|
@ -22,14 +22,15 @@ import ch.dissem.bitmessage.entity.payload.GenericPayload
|
|||||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
|
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
|
||||||
import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException
|
import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException
|
||||||
import ch.dissem.bitmessage.ports.MultiThreadedPOWEngine
|
import ch.dissem.bitmessage.ports.MultiThreadedPOWEngine
|
||||||
import ch.dissem.bitmessage.ports.ProofOfWorkEngine
|
import ch.dissem.bitmessage.utils.CallbackWaiter
|
||||||
import ch.dissem.bitmessage.utils.*
|
import ch.dissem.bitmessage.utils.Singleton
|
||||||
|
import ch.dissem.bitmessage.utils.TestUtils
|
||||||
|
import ch.dissem.bitmessage.utils.UnixTime
|
||||||
import ch.dissem.bitmessage.utils.UnixTime.DAY
|
import ch.dissem.bitmessage.utils.UnixTime.DAY
|
||||||
import ch.dissem.bitmessage.utils.UnixTime.MINUTE
|
import ch.dissem.bitmessage.utils.UnixTime.MINUTE
|
||||||
import ch.dissem.bitmessage.utils.UnixTime.now
|
import ch.dissem.bitmessage.utils.UnixTime.now
|
||||||
import org.hamcrest.CoreMatchers.`is`
|
import org.junit.jupiter.api.Assertions.*
|
||||||
import org.junit.Assert.*
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.Test
|
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import javax.xml.bind.DatatypeConverter
|
import javax.xml.bind.DatatypeConverter
|
||||||
|
|
||||||
@ -63,7 +64,7 @@ class CryptographyTest {
|
|||||||
assertArrayEquals(crypto.sha512(TEST_SHA512), crypto.doubleSha512(TEST_VALUE))
|
assertArrayEquals(crypto.sha512(TEST_SHA512), crypto.doubleSha512(TEST_VALUE))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = InsufficientProofOfWorkException::class)
|
@Test
|
||||||
fun `ensure exception for insufficient proof of work`() {
|
fun `ensure exception for insufficient proof of work`() {
|
||||||
val objectMessage = ObjectMessage.Builder()
|
val objectMessage = ObjectMessage.Builder()
|
||||||
.nonce(ByteArray(8))
|
.nonce(ByteArray(8))
|
||||||
@ -71,8 +72,10 @@ class CryptographyTest {
|
|||||||
.objectType(0)
|
.objectType(0)
|
||||||
.payload(GenericPayload.read(0, 1, ByteArrayInputStream(ByteArray(0)), 0))
|
.payload(GenericPayload.read(0, 1, ByteArrayInputStream(ByteArray(0)), 0))
|
||||||
.build()
|
.build()
|
||||||
|
assertThrows(InsufficientProofOfWorkException::class.java) {
|
||||||
crypto.checkProofOfWork(objectMessage, 1000, 1000)
|
crypto.checkProofOfWork(objectMessage, 1000, 1000)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `ensure proof of work is calculated correctly`() {
|
fun `ensure proof of work is calculated correctly`() {
|
||||||
@ -89,17 +92,12 @@ class CryptographyTest {
|
|||||||
stream = 1
|
stream = 1
|
||||||
)
|
)
|
||||||
val waiter = CallbackWaiter<ByteArray>()
|
val waiter = CallbackWaiter<ByteArray>()
|
||||||
crypto.doProofOfWork(objectMessage, 1000, 1000,
|
crypto.doProofOfWork(objectMessage, 1000, 1000) { _, nonce -> waiter.setValue(nonce) }
|
||||||
object : ProofOfWorkEngine.Callback {
|
|
||||||
override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) {
|
|
||||||
waiter.setValue(nonce)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
objectMessage.nonce = waiter.waitForValue()
|
objectMessage.nonce = waiter.waitForValue()
|
||||||
try {
|
try {
|
||||||
crypto.checkProofOfWork(objectMessage, 1000, 1000)
|
crypto.checkProofOfWork(objectMessage, 1000, 1000)
|
||||||
} catch (e: InsufficientProofOfWorkException) {
|
} catch (e: InsufficientProofOfWorkException) {
|
||||||
fail(e.message)
|
fail<Unit>(e.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,13 +111,15 @@ class CryptographyTest {
|
|||||||
assertArrayEquals(data, decrypted)
|
assertArrayEquals(data, decrypted)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException::class)
|
@Test
|
||||||
fun `ensure decryption fails with invalid cypher text`() {
|
fun `ensure decryption fails with invalid cypher text`() {
|
||||||
val data = crypto.randomBytes(128)
|
val data = crypto.randomBytes(128)
|
||||||
val key_e = crypto.randomBytes(32)
|
val key_e = crypto.randomBytes(32)
|
||||||
val iv = crypto.randomBytes(16)
|
val iv = crypto.randomBytes(16)
|
||||||
|
assertThrows(IllegalArgumentException::class.java) {
|
||||||
crypto.crypt(false, data, key_e, iv)
|
crypto.crypt(false, data, key_e, iv)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `ensure multiplication works correctly`() {
|
fun `ensure multiplication works correctly`() {
|
||||||
@ -137,7 +137,7 @@ class CryptographyTest {
|
|||||||
val data = crypto.randomBytes(100)
|
val data = crypto.randomBytes(100)
|
||||||
val privateKey = PrivateKey(false, 1, 1000, 1000)
|
val privateKey = PrivateKey(false, 1, 1000, 1000)
|
||||||
val signature = crypto.getSignature(data, privateKey)
|
val signature = crypto.getSignature(data, privateKey)
|
||||||
assertThat(crypto.isSignatureValid(data, signature, privateKey.pubkey), `is`(true))
|
assertTrue(crypto.isSignatureValid(data, signature, privateKey.pubkey))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -146,18 +146,24 @@ class CryptographyTest {
|
|||||||
val privateKey = PrivateKey(false, 1, 1000, 1000)
|
val privateKey = PrivateKey(false, 1, 1000, 1000)
|
||||||
val signature = crypto.getSignature(data, privateKey)
|
val signature = crypto.getSignature(data, privateKey)
|
||||||
data[0]++
|
data[0]++
|
||||||
assertThat(crypto.isSignatureValid(data, signature, privateKey.pubkey), `is`(false))
|
assertFalse(crypto.isSignatureValid(data, signature, privateKey.pubkey))
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val TEST_VALUE = "teststring".toByteArray()
|
val TEST_VALUE = "teststring".toByteArray()
|
||||||
val TEST_SHA1 = DatatypeConverter.parseHexBinary(""
|
val TEST_SHA1 = DatatypeConverter.parseHexBinary(
|
||||||
+ "b8473b86d4c2072ca9b08bd28e373e8253e865c4")
|
""
|
||||||
val TEST_SHA512 = DatatypeConverter.parseHexBinary(""
|
+ "b8473b86d4c2072ca9b08bd28e373e8253e865c4"
|
||||||
|
)
|
||||||
|
val TEST_SHA512 = DatatypeConverter.parseHexBinary(
|
||||||
|
""
|
||||||
+ "6253b39071e5df8b5098f59202d414c37a17d6a38a875ef5f8c7d89b0212b028"
|
+ "6253b39071e5df8b5098f59202d414c37a17d6a38a875ef5f8c7d89b0212b028"
|
||||||
+ "692d3d2090ce03ae1de66c862fa8a561e57ed9eb7935ce627344f742c0931d72")
|
+ "692d3d2090ce03ae1de66c862fa8a561e57ed9eb7935ce627344f742c0931d72"
|
||||||
val TEST_RIPEMD160 = DatatypeConverter.parseHexBinary(""
|
)
|
||||||
+ "cd566972b5e50104011a92b59fa8e0b1234851ae")
|
val TEST_RIPEMD160 = DatatypeConverter.parseHexBinary(
|
||||||
|
""
|
||||||
|
+ "cd566972b5e50104011a92b59fa8e0b1234851ae"
|
||||||
|
)
|
||||||
|
|
||||||
private val crypto = BouncyCryptography()
|
private val crypto = BouncyCryptography()
|
||||||
|
|
||||||
|
@ -13,7 +13,8 @@ uploadArchives {
|
|||||||
dependencies {
|
dependencies {
|
||||||
compile project(':core')
|
compile project(':core')
|
||||||
compile 'com.madgag.spongycastle:prov'
|
compile 'com.madgag.spongycastle:prov'
|
||||||
testCompile 'junit:junit'
|
|
||||||
testCompile 'com.nhaarman:mockito-kotlin'
|
testCompile 'com.nhaarman:mockito-kotlin'
|
||||||
|
testCompile 'org.junit.jupiter:junit-jupiter-api'
|
||||||
|
testRuntime 'org.junit.jupiter:junit-jupiter-engine'
|
||||||
testCompile project(path: ':core', configuration: 'testArtifacts')
|
testCompile project(path: ':core', configuration: 'testArtifacts')
|
||||||
}
|
}
|
||||||
|
@ -68,12 +68,12 @@ class CryptographyTest {
|
|||||||
|
|
||||||
@Test(expected = IOException::class)
|
@Test(expected = IOException::class)
|
||||||
fun ensureExceptionForInsufficientProofOfWork() {
|
fun ensureExceptionForInsufficientProofOfWork() {
|
||||||
val objectMessage = ObjectMessage.Builder()
|
val objectMessage = ObjectMessage(
|
||||||
.nonce(ByteArray(8))
|
nonce = ByteArray(8),
|
||||||
.expiresTime(UnixTime.now + 28 * DAY)
|
expiresTime = UnixTime.now + 28 * DAY,
|
||||||
.objectType(0)
|
payload = GenericPayload.read(0, 1, ByteArrayInputStream(ByteArray(0)), 0),
|
||||||
.payload(GenericPayload.read(0, 1, ByteArrayInputStream(ByteArray(0)), 0))
|
type = 0
|
||||||
.build()
|
)
|
||||||
crypto.checkProofOfWork(objectMessage, 1000, 1000)
|
crypto.checkProofOfWork(objectMessage, 1000, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,10 +86,8 @@ class CryptographyTest {
|
|||||||
val objectMessage = ObjectMessage(
|
val objectMessage = ObjectMessage(
|
||||||
nonce = ByteArray(8),
|
nonce = ByteArray(8),
|
||||||
expiresTime = UnixTime.now + 2 * MINUTE,
|
expiresTime = UnixTime.now + 2 * MINUTE,
|
||||||
type = 0,
|
|
||||||
payload = GenericPayload.read(0, 1, ByteArrayInputStream(ByteArray(0)), 0),
|
payload = GenericPayload.read(0, 1, ByteArrayInputStream(ByteArray(0)), 0),
|
||||||
version = 0,
|
type = 0
|
||||||
stream = 1
|
|
||||||
)
|
)
|
||||||
val waiter = CallbackWaiter<ByteArray>()
|
val waiter = CallbackWaiter<ByteArray>()
|
||||||
crypto.doProofOfWork(objectMessage, 1000, 1000,
|
crypto.doProofOfWork(objectMessage, 1000, 1000,
|
||||||
@ -154,13 +152,19 @@ class CryptographyTest {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val TEST_VALUE = "teststring".toByteArray()
|
val TEST_VALUE = "teststring".toByteArray()
|
||||||
val TEST_SHA1 = DatatypeConverter.parseHexBinary(""
|
val TEST_SHA1 = DatatypeConverter.parseHexBinary(
|
||||||
+ "b8473b86d4c2072ca9b08bd28e373e8253e865c4")
|
""
|
||||||
val TEST_SHA512 = DatatypeConverter.parseHexBinary(""
|
+ "b8473b86d4c2072ca9b08bd28e373e8253e865c4"
|
||||||
|
)
|
||||||
|
val TEST_SHA512 = DatatypeConverter.parseHexBinary(
|
||||||
|
""
|
||||||
+ "6253b39071e5df8b5098f59202d414c37a17d6a38a875ef5f8c7d89b0212b028"
|
+ "6253b39071e5df8b5098f59202d414c37a17d6a38a875ef5f8c7d89b0212b028"
|
||||||
+ "692d3d2090ce03ae1de66c862fa8a561e57ed9eb7935ce627344f742c0931d72")
|
+ "692d3d2090ce03ae1de66c862fa8a561e57ed9eb7935ce627344f742c0931d72"
|
||||||
val TEST_RIPEMD160 = DatatypeConverter.parseHexBinary(""
|
)
|
||||||
+ "cd566972b5e50104011a92b59fa8e0b1234851ae")
|
val TEST_RIPEMD160 = DatatypeConverter.parseHexBinary(
|
||||||
|
""
|
||||||
|
+ "cd566972b5e50104011a92b59fa8e0b1234851ae"
|
||||||
|
)
|
||||||
|
|
||||||
private val crypto = SpongyCryptography()
|
private val crypto = SpongyCryptography()
|
||||||
|
|
||||||
|
@ -24,15 +24,16 @@ task fatCapsule(type: FatCapsule) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':core')
|
implementation project(':core')
|
||||||
compile project(':networking')
|
implementation project(':networking')
|
||||||
compile project(':repositories')
|
implementation project(':repositories')
|
||||||
compile project(':cryptography-bc')
|
implementation project(':cryptography-bc')
|
||||||
compile project(':wif')
|
implementation project(':wif')
|
||||||
compile 'org.slf4j:slf4j-simple'
|
implementation 'org.slf4j:slf4j-simple'
|
||||||
compile 'args4j:args4j'
|
implementation 'args4j:args4j'
|
||||||
compile 'com.h2database:h2'
|
implementation 'com.h2database:h2'
|
||||||
compile 'org.apache.commons:commons-lang3'
|
implementation 'org.apache.commons:commons-text'
|
||||||
testCompile 'junit:junit'
|
testImplementation 'com.nhaarman:mockito-kotlin'
|
||||||
testCompile 'com.nhaarman:mockito-kotlin'
|
testImplementation 'org.junit.jupiter:junit-jupiter-api'
|
||||||
|
testRuntime 'org.junit.jupiter:junit-jupiter-engine'
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ import ch.dissem.bitmessage.entity.Plaintext;
|
|||||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
||||||
import ch.dissem.bitmessage.entity.valueobject.Label;
|
import ch.dissem.bitmessage.entity.valueobject.Label;
|
||||||
import ch.dissem.bitmessage.entity.valueobject.extended.Message;
|
import ch.dissem.bitmessage.entity.valueobject.extended.Message;
|
||||||
import org.apache.commons.lang3.text.WordUtils;
|
import org.apache.commons.text.WordUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@ -115,6 +115,7 @@ public class Application {
|
|||||||
System.out.println(ctx.status());
|
System.out.println(ctx.status());
|
||||||
System.out.println();
|
System.out.println();
|
||||||
System.out.println("c) cleanup inventory");
|
System.out.println("c) cleanup inventory");
|
||||||
|
System.out.println("n) remove known nodes");
|
||||||
System.out.println("r) resend unacknowledged messages");
|
System.out.println("r) resend unacknowledged messages");
|
||||||
System.out.println(COMMAND_BACK);
|
System.out.println(COMMAND_BACK);
|
||||||
|
|
||||||
@ -123,6 +124,9 @@ public class Application {
|
|||||||
case "c":
|
case "c":
|
||||||
ctx.cleanup();
|
ctx.cleanup();
|
||||||
break;
|
break;
|
||||||
|
case "n":
|
||||||
|
ctx.internals().getNodeRegistry().cleanup();
|
||||||
|
break;
|
||||||
case "r":
|
case "r":
|
||||||
ctx.resendUnacknowledgedMessages();
|
ctx.resendUnacknowledgedMessages();
|
||||||
break;
|
break;
|
||||||
@ -285,7 +289,7 @@ public class Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void labels() {
|
private void labels() {
|
||||||
List<Label> labels = ctx.messages().getLabels();
|
List<Label> labels = ctx.labels().getLabels();
|
||||||
String command;
|
String command;
|
||||||
do {
|
do {
|
||||||
System.out.println();
|
System.out.println();
|
||||||
|
@ -24,6 +24,7 @@ import ch.dissem.bitmessage.ports.NodeRegistry;
|
|||||||
import ch.dissem.bitmessage.repository.*;
|
import ch.dissem.bitmessage.repository.*;
|
||||||
import ch.dissem.bitmessage.wif.WifExporter;
|
import ch.dissem.bitmessage.wif.WifExporter;
|
||||||
import ch.dissem.bitmessage.wif.WifImporter;
|
import ch.dissem.bitmessage.wif.WifImporter;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.kohsuke.args4j.CmdLineException;
|
import org.kohsuke.args4j.CmdLineException;
|
||||||
import org.kohsuke.args4j.CmdLineParser;
|
import org.kohsuke.args4j.CmdLineParser;
|
||||||
import org.kohsuke.args4j.Option;
|
import org.kohsuke.args4j.Option;
|
||||||
@ -62,18 +63,35 @@ public class Main {
|
|||||||
.inventory(new JdbcInventory(jdbcConfig))
|
.inventory(new JdbcInventory(jdbcConfig))
|
||||||
.messageRepo(new JdbcMessageRepository(jdbcConfig))
|
.messageRepo(new JdbcMessageRepository(jdbcConfig))
|
||||||
.powRepo(new JdbcProofOfWorkRepository(jdbcConfig))
|
.powRepo(new JdbcProofOfWorkRepository(jdbcConfig))
|
||||||
|
.labelRepo(new JdbcLabelRepository(jdbcConfig))
|
||||||
.networkHandler(new NioNetworkHandler())
|
.networkHandler(new NioNetworkHandler())
|
||||||
.cryptography(new BouncyCryptography())
|
.cryptography(new BouncyCryptography());
|
||||||
.port(48444);
|
ctxBuilder.getPreferences().setPort(48444);
|
||||||
if (options.localPort != null) {
|
if (options.localPort != null) {
|
||||||
ctxBuilder.nodeRegistry(new NodeRegistry() {
|
ctxBuilder.nodeRegistry(new NodeRegistry() {
|
||||||
|
@Override
|
||||||
|
public void cleanup() {
|
||||||
|
// NO OP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(@NotNull NetworkAddress node) {
|
||||||
|
// NO OP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(@NotNull NetworkAddress node) {
|
||||||
|
// NO OP
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clear() {
|
public void clear() {
|
||||||
// NO OP
|
// NO OP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public List<NetworkAddress> getKnownAddresses(int limit, long... streams) {
|
public List<NetworkAddress> getKnownAddresses(int limit, @NotNull long... streams) {
|
||||||
return Arrays.stream(streams)
|
return Arrays.stream(streams)
|
||||||
.mapToObj(s -> new NetworkAddress.Builder()
|
.mapToObj(s -> new NetworkAddress.Builder()
|
||||||
.ipv4(127, 0, 0, 1)
|
.ipv4(127, 0, 0, 1)
|
||||||
@ -83,7 +101,7 @@ public class Main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void offerAddresses(List<NetworkAddress> nodes) {
|
public void offerAddresses(@NotNull List<NetworkAddress> nodes) {
|
||||||
LOG.info("Local node registry ignored offered addresses: " + nodes);
|
LOG.info("Local node registry ignored offered addresses: " + nodes);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -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() + ")";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
215
demo/src/test/java/ch/dissem/bitmessage/SystemTest.kt
Normal file
215
demo/src/test/java/ch/dissem/bitmessage/SystemTest.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
@ -18,6 +18,7 @@ package ch.dissem.bitmessage;
|
|||||||
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
||||||
import ch.dissem.bitmessage.ports.NodeRegistry;
|
import ch.dissem.bitmessage.ports.NodeRegistry;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -44,13 +45,29 @@ class TestNodeRegistry implements NodeRegistry {
|
|||||||
// NO OP
|
// NO OP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public List<NetworkAddress> getKnownAddresses(int limit, long... streams) {
|
public List<NetworkAddress> getKnownAddresses(int limit, @NotNull long... streams) {
|
||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void offerAddresses(List<NetworkAddress> nodes) {
|
public void offerAddresses(@NotNull List<NetworkAddress> nodes) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(@NotNull NetworkAddress node) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(@NotNull NetworkAddress node) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cleanup() {
|
||||||
// Ignore
|
// Ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,9 +15,10 @@ dependencies {
|
|||||||
compile 'org.slf4j:slf4j-api'
|
compile 'org.slf4j:slf4j-api'
|
||||||
compile 'com.beust:klaxon'
|
compile 'com.beust:klaxon'
|
||||||
|
|
||||||
testCompile 'junit:junit:4.12'
|
testCompile 'com.nhaarman:mockito-kotlin'
|
||||||
testCompile 'org.hamcrest:hamcrest-library:1.3'
|
testCompile 'org.junit.jupiter:junit-jupiter-api'
|
||||||
testCompile 'com.nhaarman:mockito-kotlin:1.5.0'
|
testRuntime 'org.junit.jupiter:junit-jupiter-engine'
|
||||||
|
|
||||||
testCompile project(path: ':core', configuration: 'testArtifacts')
|
testCompile project(path: ':core', configuration: 'testArtifacts')
|
||||||
testCompile project(':cryptography-bc')
|
testCompile project(':cryptography-bc')
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ object ContactExport {
|
|||||||
"subscribed" to it.isSubscribed,
|
"subscribed" to it.isSubscribed,
|
||||||
"pubkey" to it.pubkey?.let {
|
"pubkey" to it.pubkey?.let {
|
||||||
val out = ByteArrayOutputStream()
|
val out = ByteArrayOutputStream()
|
||||||
it.writeUnencrypted(out)
|
it.writer().writeUnencrypted(out)
|
||||||
Base64.encodeToString(out.toByteArray())
|
Base64.encodeToString(out.toByteArray())
|
||||||
},
|
},
|
||||||
"privateKey" to if (includePrivateKey) {
|
"privateKey" to if (includePrivateKey) {
|
||||||
@ -68,7 +68,7 @@ object ContactExport {
|
|||||||
Factory.readPubkey(
|
Factory.readPubkey(
|
||||||
version = version,
|
version = version,
|
||||||
stream = stream,
|
stream = stream,
|
||||||
`is` = ByteArrayInputStream(it),
|
input = ByteArrayInputStream(it),
|
||||||
length = it.size,
|
length = it.size,
|
||||||
encrypted = false
|
encrypted = false
|
||||||
)
|
)
|
||||||
|
@ -19,10 +19,8 @@ package ch.dissem.bitmessage.exports
|
|||||||
import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography
|
import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
import ch.dissem.bitmessage.utils.TestUtils
|
import ch.dissem.bitmessage.utils.TestUtils
|
||||||
import org.hamcrest.CoreMatchers.`is`
|
import org.junit.jupiter.api.Assertions.*
|
||||||
import org.hamcrest.CoreMatchers.nullValue
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.Assert.assertThat
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
class ContactExportTest {
|
class ContactExportTest {
|
||||||
|
|
||||||
@ -42,7 +40,7 @@ class ContactExportTest {
|
|||||||
)
|
)
|
||||||
val export = ContactExport.exportContacts(contacts)
|
val export = ContactExport.exportContacts(contacts)
|
||||||
print(export.toJsonString(true))
|
print(export.toJsonString(true))
|
||||||
assertThat(ContactExport.importContacts(export), `is`(contacts))
|
assertEquals(contacts, ContactExport.importContacts(export))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -53,9 +51,9 @@ class ContactExportTest {
|
|||||||
val export = ContactExport.exportContacts(contacts)
|
val export = ContactExport.exportContacts(contacts)
|
||||||
print(export.toJsonString(true))
|
print(export.toJsonString(true))
|
||||||
val import = ContactExport.importContacts(export)
|
val import = ContactExport.importContacts(export)
|
||||||
assertThat(import.size, `is`(1))
|
assertEquals(1, import.size)
|
||||||
assertThat(import[0].isChan, `is`(true))
|
assertTrue(import[0].isChan)
|
||||||
assertThat(import[0].privateKey, `is`(nullValue()))
|
assertNull(import[0].privateKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -66,8 +64,9 @@ class ContactExportTest {
|
|||||||
val export = ContactExport.exportContacts(contacts, true)
|
val export = ContactExport.exportContacts(contacts, true)
|
||||||
print(export.toJsonString(true))
|
print(export.toJsonString(true))
|
||||||
val import = ContactExport.importContacts(export)
|
val import = ContactExport.importContacts(export)
|
||||||
assertThat(import.size, `is`(1))
|
|
||||||
assertThat(import[0].isChan, `is`(true))
|
assertEquals(1, import.size)
|
||||||
assertThat(import[0].privateKey, `is`(contacts[0].privateKey))
|
assertTrue(import[0].isChan)
|
||||||
|
assertEquals(contacts[0].privateKey, import[0].privateKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,23 +23,22 @@ import ch.dissem.bitmessage.entity.valueobject.Label
|
|||||||
import ch.dissem.bitmessage.utils.ConversationServiceTest
|
import ch.dissem.bitmessage.utils.ConversationServiceTest
|
||||||
import ch.dissem.bitmessage.utils.Singleton
|
import ch.dissem.bitmessage.utils.Singleton
|
||||||
import ch.dissem.bitmessage.utils.TestUtils
|
import ch.dissem.bitmessage.utils.TestUtils
|
||||||
import org.hamcrest.CoreMatchers.`is`
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.Assert.assertThat
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
class MessageExportTest {
|
class MessageExportTest {
|
||||||
val inbox = Label("Inbox", Label.Type.INBOX, 0x0000ff)
|
private val inbox = Label("Inbox", Label.Type.INBOX, 0x0000ff)
|
||||||
val outbox = Label("Outbox", Label.Type.OUTBOX, 0x00ff00)
|
private val outbox = Label("Outbox", Label.Type.OUTBOX, 0x00ff00)
|
||||||
val unread = Label("Unread", Label.Type.UNREAD, 0x000000)
|
private val unread = Label("Unread", Label.Type.UNREAD, 0x000000)
|
||||||
val trash = Label("Trash", Label.Type.TRASH, 0x555555)
|
private val trash = Label("Trash", Label.Type.TRASH, 0x555555)
|
||||||
|
|
||||||
val labels = listOf(
|
private val labels = listOf(
|
||||||
inbox,
|
inbox,
|
||||||
outbox,
|
outbox,
|
||||||
unread,
|
unread,
|
||||||
trash
|
trash
|
||||||
)
|
)
|
||||||
val labelMap = MessageExport.createLabelMap(labels)
|
private val labelMap = MessageExport.createLabelMap(labels)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
TestUtils.mockedInternalContext(cryptography = BouncyCryptography())
|
TestUtils.mockedInternalContext(cryptography = BouncyCryptography())
|
||||||
@ -49,7 +48,7 @@ class MessageExportTest {
|
|||||||
fun `ensure labels are exported`() {
|
fun `ensure labels are exported`() {
|
||||||
val export = MessageExport.exportLabels(labels)
|
val export = MessageExport.exportLabels(labels)
|
||||||
print(export.toJsonString(true))
|
print(export.toJsonString(true))
|
||||||
assertThat(MessageExport.importLabels(export), `is`(labels))
|
assertEquals(labels, MessageExport.importLabels(export))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -84,6 +83,6 @@ class MessageExportTest {
|
|||||||
)
|
)
|
||||||
val export = MessageExport.exportMessages(messages)
|
val export = MessageExport.exportMessages(messages)
|
||||||
print(export.toJsonString(true))
|
print(export.toJsonString(true))
|
||||||
assertThat(MessageExport.importMessages(export, labelMap), `is`(messages))
|
assertEquals(messages, MessageExport.importMessages(export, labelMap))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,9 +28,10 @@ uploadArchives {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':core')
|
compile project(':core')
|
||||||
testCompile 'junit:junit'
|
|
||||||
testCompile 'org.slf4j:slf4j-simple'
|
testCompile 'org.slf4j:slf4j-simple'
|
||||||
testCompile 'com.nhaarman:mockito-kotlin'
|
testCompile 'com.nhaarman:mockito-kotlin'
|
||||||
|
testCompile 'org.junit.jupiter:junit-jupiter-api'
|
||||||
|
testRuntime 'org.junit.jupiter:junit-jupiter-engine'
|
||||||
testCompile project(path: ':core', configuration: 'testArtifacts')
|
testCompile project(path: ':core', configuration: 'testArtifacts')
|
||||||
testCompile project(':cryptography-bc')
|
testCompile project(':cryptography-bc')
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ package ch.dissem.bitmessage.extensions
|
|||||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
import ch.dissem.bitmessage.entity.CustomMessage
|
import ch.dissem.bitmessage.entity.CustomMessage
|
||||||
import ch.dissem.bitmessage.entity.Streamable
|
import ch.dissem.bitmessage.entity.Streamable
|
||||||
|
import ch.dissem.bitmessage.entity.StreamableWriter
|
||||||
import ch.dissem.bitmessage.entity.payload.CryptoBox
|
import ch.dissem.bitmessage.entity.payload.CryptoBox
|
||||||
import ch.dissem.bitmessage.entity.payload.Pubkey
|
import ch.dissem.bitmessage.entity.payload.Pubkey
|
||||||
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
||||||
@ -41,19 +42,21 @@ import java.io.OutputStream
|
|||||||
*/
|
*/
|
||||||
class CryptoCustomMessage<T : Streamable> : CustomMessage {
|
class CryptoCustomMessage<T : Streamable> : CustomMessage {
|
||||||
|
|
||||||
private val dataReader: Reader<T>?
|
private val dataReader: (BitmessageAddress, InputStream) -> T
|
||||||
private var container: CryptoBox? = null
|
private var container: CryptoBox? = null
|
||||||
var sender: BitmessageAddress? = null
|
var sender: BitmessageAddress? = null
|
||||||
private set
|
private set
|
||||||
private var data: T? = null
|
private var data: T? = null
|
||||||
private set
|
|
||||||
|
|
||||||
constructor(data: T) : super(COMMAND, null) {
|
constructor(data: T) : super(COMMAND, null) {
|
||||||
this.data = data
|
this.data = data
|
||||||
this.dataReader = null
|
this.dataReader = { _, _ -> data }
|
||||||
}
|
}
|
||||||
|
|
||||||
private constructor(container: CryptoBox, dataReader: Reader<T>) : super(COMMAND, null) {
|
private constructor(container: CryptoBox, dataReader: (BitmessageAddress, InputStream) -> T) : super(
|
||||||
|
COMMAND,
|
||||||
|
null
|
||||||
|
) {
|
||||||
this.container = container
|
this.container = container
|
||||||
this.dataReader = dataReader
|
this.dataReader = dataReader
|
||||||
}
|
}
|
||||||
@ -73,25 +76,27 @@ class CryptoCustomMessage<T : Streamable> : CustomMessage {
|
|||||||
Encode.varInt(privateKey.pubkey.extraBytes, out)
|
Encode.varInt(privateKey.pubkey.extraBytes, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
data?.write(out) ?: throw IllegalStateException("no unencrypted data available")
|
data?.writer()?.write(out) ?: throw IllegalStateException("no unencrypted data available")
|
||||||
Encode.varBytes(cryptography().getSignature(out.toByteArray(), privateKey), out)
|
Encode.varBytes(cryptography().getSignature(out.toByteArray(), privateKey), out)
|
||||||
container = CryptoBox(out.toByteArray(), publicKey)
|
container = CryptoBox(out.toByteArray(), publicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(DecryptionFailedException::class)
|
@Throws(DecryptionFailedException::class)
|
||||||
fun decrypt(privateKey: ByteArray): T {
|
fun decrypt(privateKey: ByteArray): T {
|
||||||
val `in` = SignatureCheckingInputStream(container?.decrypt(privateKey) ?: throw IllegalStateException("no encrypted data available"))
|
val input = SignatureCheckingInputStream(
|
||||||
if (dataReader == null) throw IllegalStateException("no data reader available")
|
container?.decrypt(privateKey) ?: throw IllegalStateException("no encrypted data available")
|
||||||
|
)
|
||||||
|
|
||||||
val addressVersion = varInt(`in`)
|
val addressVersion = varInt(input)
|
||||||
val stream = varInt(`in`)
|
val stream = varInt(input)
|
||||||
val behaviorBitfield = int32(`in`)
|
val behaviorBitfield = int32(input)
|
||||||
val publicSigningKey = bytes(`in`, 64)
|
val publicSigningKey = bytes(input, 64)
|
||||||
val publicEncryptionKey = bytes(`in`, 64)
|
val publicEncryptionKey = bytes(input, 64)
|
||||||
val nonceTrialsPerByte = if (addressVersion >= 3) varInt(`in`) else 0
|
val nonceTrialsPerByte = if (addressVersion >= 3) varInt(input) else 0
|
||||||
val extraBytes = if (addressVersion >= 3) varInt(`in`) else 0
|
val extraBytes = if (addressVersion >= 3) varInt(input) else 0
|
||||||
|
|
||||||
val sender = BitmessageAddress(Factory.createPubkey(
|
val sender = BitmessageAddress(
|
||||||
|
Factory.createPubkey(
|
||||||
addressVersion,
|
addressVersion,
|
||||||
stream,
|
stream,
|
||||||
publicSigningKey,
|
publicSigningKey,
|
||||||
@ -99,26 +104,36 @@ class CryptoCustomMessage<T : Streamable> : CustomMessage {
|
|||||||
nonceTrialsPerByte,
|
nonceTrialsPerByte,
|
||||||
extraBytes,
|
extraBytes,
|
||||||
behaviorBitfield
|
behaviorBitfield
|
||||||
))
|
)
|
||||||
|
)
|
||||||
this.sender = sender
|
this.sender = sender
|
||||||
|
|
||||||
data = dataReader.read(sender, `in`)
|
data = dataReader.invoke(sender, input)
|
||||||
|
|
||||||
`in`.checkSignature(sender.pubkey!!)
|
input.checkSignature(sender.pubkey!!)
|
||||||
|
|
||||||
return data!!
|
return data!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun writer(): StreamableWriter = Writer(this)
|
||||||
|
|
||||||
|
private class Writer(
|
||||||
|
private val item: CryptoCustomMessage<*>
|
||||||
|
) : CustomMessage.Writer(item) {
|
||||||
|
|
||||||
override fun write(out: OutputStream) {
|
override fun write(out: OutputStream) {
|
||||||
Encode.varString(COMMAND, out)
|
Encode.varString(COMMAND, out)
|
||||||
container?.write(out) ?: throw IllegalStateException("not encrypted yet")
|
item.container?.writer()?.write(out) ?: throw IllegalStateException("not encrypted yet")
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Reader<out T> {
|
interface Reader<out T> {
|
||||||
fun read(sender: BitmessageAddress, `in`: InputStream): T
|
fun read(sender: BitmessageAddress, input: InputStream): T
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class SignatureCheckingInputStream internal constructor(private val wrapped: InputStream) : InputStream() {
|
private inner class SignatureCheckingInputStream internal constructor(private val wrapped: InputStream) :
|
||||||
|
InputStream() {
|
||||||
private val out = ByteArrayOutputStream()
|
private val out = ByteArrayOutputStream()
|
||||||
|
|
||||||
override fun read(): Int {
|
override fun read(): Int {
|
||||||
@ -136,11 +151,24 @@ class CryptoCustomMessage<T : Streamable> : CustomMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmField val COMMAND = "ENCRYPTED"
|
const val COMMAND = "ENCRYPTED"
|
||||||
|
|
||||||
@JvmStatic fun <T : Streamable> read(data: CustomMessage, dataReader: Reader<T>): CryptoCustomMessage<T> {
|
@JvmStatic
|
||||||
val cryptoBox = CryptoBox.read(ByteArrayInputStream(data.getData()), data.getData().size)
|
fun <T : Streamable> read(data: CustomMessage, dataReader: Reader<T>): CryptoCustomMessage<T> =
|
||||||
return CryptoCustomMessage(cryptoBox, dataReader)
|
CryptoCustomMessage(
|
||||||
}
|
CryptoBox.read(ByteArrayInputStream(data.getData()), data.getData().size)
|
||||||
|
) { address, input ->
|
||||||
|
dataReader.read(address, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmSynthetic
|
||||||
|
fun <T : Streamable> read(
|
||||||
|
data: CustomMessage,
|
||||||
|
dataReader: (BitmessageAddress, InputStream) -> T
|
||||||
|
): CryptoCustomMessage<T> =
|
||||||
|
CryptoCustomMessage(
|
||||||
|
CryptoBox.read(ByteArrayInputStream(data.getData()), data.getData().size),
|
||||||
|
dataReader
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ package ch.dissem.bitmessage.extensions.pow
|
|||||||
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
import ch.dissem.bitmessage.entity.Streamable
|
import ch.dissem.bitmessage.entity.Streamable
|
||||||
|
import ch.dissem.bitmessage.entity.StreamableWriter
|
||||||
import ch.dissem.bitmessage.extensions.CryptoCustomMessage
|
import ch.dissem.bitmessage.extensions.CryptoCustomMessage
|
||||||
import ch.dissem.bitmessage.utils.Decode.bytes
|
import ch.dissem.bitmessage.utils.Decode.bytes
|
||||||
import ch.dissem.bitmessage.utils.Decode.varBytes
|
import ch.dissem.bitmessage.utils.Decode.varBytes
|
||||||
@ -33,22 +34,30 @@ import java.util.*
|
|||||||
*/
|
*/
|
||||||
data class ProofOfWorkRequest @JvmOverloads constructor(val sender: BitmessageAddress, val initialHash: ByteArray, val request: ProofOfWorkRequest.Request, val data: ByteArray = ByteArray(0)) : Streamable {
|
data class ProofOfWorkRequest @JvmOverloads constructor(val sender: BitmessageAddress, val initialHash: ByteArray, val request: ProofOfWorkRequest.Request, val data: ByteArray = ByteArray(0)) : Streamable {
|
||||||
|
|
||||||
|
override fun writer(): StreamableWriter = Writer(this)
|
||||||
|
|
||||||
|
private class Writer(
|
||||||
|
private val item: ProofOfWorkRequest
|
||||||
|
) : StreamableWriter {
|
||||||
|
|
||||||
override fun write(out: OutputStream) {
|
override fun write(out: OutputStream) {
|
||||||
out.write(initialHash)
|
out.write(item.initialHash)
|
||||||
Encode.varString(request.name, out)
|
Encode.varString(item.request.name, out)
|
||||||
Encode.varBytes(data, out)
|
Encode.varBytes(item.data, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun write(buffer: ByteBuffer) {
|
override fun write(buffer: ByteBuffer) {
|
||||||
buffer.put(initialHash)
|
buffer.put(item.initialHash)
|
||||||
Encode.varString(request.name, buffer)
|
Encode.varString(item.request.name, buffer)
|
||||||
Encode.varBytes(data, buffer)
|
Encode.varBytes(item.data, buffer)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Reader(private val identity: BitmessageAddress) : CryptoCustomMessage.Reader<ProofOfWorkRequest> {
|
class Reader(private val identity: BitmessageAddress) : CryptoCustomMessage.Reader<ProofOfWorkRequest> {
|
||||||
|
|
||||||
override fun read(sender: BitmessageAddress, `in`: InputStream): ProofOfWorkRequest {
|
override fun read(sender: BitmessageAddress, input: InputStream): ProofOfWorkRequest {
|
||||||
return ProofOfWorkRequest.read(identity, `in`)
|
return ProofOfWorkRequest.read(identity, input)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,12 +87,12 @@ data class ProofOfWorkRequest @JvmOverloads constructor(val sender: BitmessageAd
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun read(client: BitmessageAddress, `in`: InputStream): ProofOfWorkRequest {
|
fun read(client: BitmessageAddress, input: InputStream): ProofOfWorkRequest {
|
||||||
return ProofOfWorkRequest(
|
return ProofOfWorkRequest(
|
||||||
client,
|
client,
|
||||||
bytes(`in`, 64),
|
bytes(input, 64),
|
||||||
Request.valueOf(varString(`in`)),
|
Request.valueOf(varString(input)),
|
||||||
varBytes(`in`)
|
varBytes(input)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,17 +21,15 @@ import ch.dissem.bitmessage.entity.CustomMessage
|
|||||||
import ch.dissem.bitmessage.entity.payload.GenericPayload
|
import ch.dissem.bitmessage.entity.payload.GenericPayload
|
||||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
|
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
|
||||||
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest
|
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest
|
||||||
|
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||||
import ch.dissem.bitmessage.utils.TestBase
|
import ch.dissem.bitmessage.utils.TestBase
|
||||||
import ch.dissem.bitmessage.utils.TestUtils
|
import ch.dissem.bitmessage.utils.TestUtils
|
||||||
import org.junit.Test
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
|
||||||
import org.junit.Assert.assertEquals
|
|
||||||
|
|
||||||
class CryptoCustomMessageTest : TestBase() {
|
class CryptoCustomMessageTest : TestBase() {
|
||||||
@Test
|
@Test
|
||||||
fun `ensure encrypt then decrypt yields same object`() {
|
fun `ensure encrypt then decrypt yields same object`() {
|
||||||
@ -40,18 +38,20 @@ class CryptoCustomMessageTest : TestBase() {
|
|||||||
|
|
||||||
val payloadBefore = GenericPayload(0, 1, cryptography().randomBytes(100))
|
val payloadBefore = GenericPayload(0, 1, cryptography().randomBytes(100))
|
||||||
val messageBefore = CryptoCustomMessage(payloadBefore)
|
val messageBefore = CryptoCustomMessage(payloadBefore)
|
||||||
messageBefore.signAndEncrypt(sendingIdentity, cryptography().createPublicKey(sendingIdentity.publicDecryptionKey))
|
messageBefore.signAndEncrypt(
|
||||||
|
sendingIdentity,
|
||||||
|
cryptography().createPublicKey(sendingIdentity.publicDecryptionKey)
|
||||||
|
)
|
||||||
|
|
||||||
val out = ByteArrayOutputStream()
|
val out = ByteArrayOutputStream()
|
||||||
messageBefore.write(out)
|
messageBefore.writer().write(out)
|
||||||
val `in` = ByteArrayInputStream(out.toByteArray())
|
val input = ByteArrayInputStream(out.toByteArray())
|
||||||
|
|
||||||
val customMessage = CustomMessage.read(`in`, out.size())
|
val customMessage = CustomMessage.read(input, out.size())
|
||||||
val messageAfter = CryptoCustomMessage.read(customMessage,
|
val messageAfter = CryptoCustomMessage.read(customMessage,
|
||||||
object : CryptoCustomMessage.Reader<GenericPayload> {
|
object : CryptoCustomMessage.Reader<GenericPayload> {
|
||||||
override fun read(sender: BitmessageAddress, `in`: InputStream): GenericPayload {
|
override fun read(sender: BitmessageAddress, input: InputStream) =
|
||||||
return GenericPayload.read(0, 1, `in`, 100)
|
GenericPayload.read(0, 1, input, 100)
|
||||||
}
|
|
||||||
})
|
})
|
||||||
val payloadAfter = messageAfter.decrypt(sendingIdentity.publicDecryptionKey)
|
val payloadAfter = messageAfter.decrypt(sendingIdentity.publicDecryptionKey)
|
||||||
|
|
||||||
@ -63,20 +63,27 @@ class CryptoCustomMessageTest : TestBase() {
|
|||||||
val privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey"))
|
val privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey"))
|
||||||
val sendingIdentity = BitmessageAddress(privateKey)
|
val sendingIdentity = BitmessageAddress(privateKey)
|
||||||
|
|
||||||
val requestBefore = ProofOfWorkRequest(sendingIdentity, cryptography().randomBytes(64),
|
val requestBefore = ProofOfWorkRequest(
|
||||||
ProofOfWorkRequest.Request.CALCULATE)
|
sendingIdentity, cryptography().randomBytes(64),
|
||||||
|
ProofOfWorkRequest.Request.CALCULATE
|
||||||
|
)
|
||||||
|
|
||||||
val messageBefore = CryptoCustomMessage(requestBefore)
|
val messageBefore = CryptoCustomMessage(requestBefore)
|
||||||
messageBefore.signAndEncrypt(sendingIdentity, cryptography().createPublicKey(sendingIdentity.publicDecryptionKey))
|
messageBefore.signAndEncrypt(
|
||||||
|
sendingIdentity,
|
||||||
|
cryptography().createPublicKey(sendingIdentity.publicDecryptionKey)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
val out = ByteArrayOutputStream()
|
val out = ByteArrayOutputStream()
|
||||||
messageBefore.write(out)
|
messageBefore.writer().write(out)
|
||||||
val `in` = ByteArrayInputStream(out.toByteArray())
|
val input = ByteArrayInputStream(out.toByteArray())
|
||||||
|
|
||||||
val customMessage = CustomMessage.read(`in`, out.size())
|
val customMessage = CustomMessage.read(input, out.size())
|
||||||
val messageAfter = CryptoCustomMessage.read(customMessage,
|
val messageAfter = CryptoCustomMessage.read(
|
||||||
ProofOfWorkRequest.Reader(sendingIdentity))
|
customMessage,
|
||||||
|
ProofOfWorkRequest.Reader(sendingIdentity)
|
||||||
|
)
|
||||||
val requestAfter = messageAfter.decrypt(sendingIdentity.publicDecryptionKey)
|
val requestAfter = messageAfter.decrypt(sendingIdentity.publicDecryptionKey)
|
||||||
|
|
||||||
assertEquals(requestBefore, requestAfter)
|
assertEquals(requestBefore, requestAfter)
|
||||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
|||||||
#Mon Jul 17 06:32:41 CEST 2017
|
#Sat Mar 03 14:35:52 CET 2018
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
|
||||||
|
@ -12,9 +12,10 @@ uploadArchives {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':core')
|
compile project(':core')
|
||||||
testCompile 'junit:junit'
|
|
||||||
testCompile 'org.slf4j:slf4j-simple'
|
testCompile 'org.slf4j:slf4j-simple'
|
||||||
testCompile 'com.nhaarman:mockito-kotlin'
|
testCompile 'com.nhaarman:mockito-kotlin'
|
||||||
|
testCompile 'org.junit.jupiter:junit-jupiter-api'
|
||||||
|
testRuntime 'org.junit.jupiter:junit-jupiter-engine'
|
||||||
testCompile project(path: ':core', configuration: 'testArtifacts')
|
testCompile project(path: ':core', configuration: 'testArtifacts')
|
||||||
testCompile project(':cryptography-bc')
|
testCompile project(':cryptography-bc')
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ import org.slf4j.LoggerFactory
|
|||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains everything used by both the old streams-oriented NetworkHandler and the new NioNetworkHandler,
|
* Contains everything used by both the old streams-oriented NetworkHandler and the new NioNetworkHandler,
|
||||||
@ -58,7 +59,7 @@ class Connection(
|
|||||||
private var lastObjectTime: Long = 0
|
private var lastObjectTime: Long = 0
|
||||||
|
|
||||||
lateinit var streams: LongArray
|
lateinit var streams: LongArray
|
||||||
protected set
|
private set
|
||||||
|
|
||||||
@Volatile var state = State.CONNECTING
|
@Volatile var state = State.CONNECTING
|
||||||
private set
|
private set
|
||||||
@ -165,11 +166,12 @@ class Connection(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// the TCP timeout starts out at 20 seconds
|
// According to the specification, the TCP timeout starts out at 20 seconds
|
||||||
// after verack messages are exchanged, the timeout is raised to 10 minutes
|
// after verack messages are exchanged, the timeout is raised to 10 minutes
|
||||||
|
// Let's tweak these numbers a bit:
|
||||||
fun isExpired(): Boolean = when (state) {
|
fun isExpired(): Boolean = when (state) {
|
||||||
State.CONNECTING -> io.lastUpdate < System.currentTimeMillis() - 20000
|
State.CONNECTING -> io.lastUpdate < System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(9)
|
||||||
State.ACTIVE -> io.lastUpdate < System.currentTimeMillis() - 600000
|
State.ACTIVE -> io.lastUpdate < System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(3)
|
||||||
State.DISCONNECTED -> true
|
State.DISCONNECTED -> true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,11 +16,11 @@
|
|||||||
|
|
||||||
package ch.dissem.bitmessage.networking.nio
|
package ch.dissem.bitmessage.networking.nio
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.constants.Network.HEADER_SIZE
|
||||||
import ch.dissem.bitmessage.entity.GetData
|
import ch.dissem.bitmessage.entity.GetData
|
||||||
import ch.dissem.bitmessage.entity.MessagePayload
|
import ch.dissem.bitmessage.entity.MessagePayload
|
||||||
import ch.dissem.bitmessage.entity.NetworkMessage
|
import ch.dissem.bitmessage.entity.NetworkMessage
|
||||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
||||||
import ch.dissem.bitmessage.exception.NodeException
|
|
||||||
import ch.dissem.bitmessage.factory.V3MessageReader
|
import ch.dissem.bitmessage.factory.V3MessageReader
|
||||||
import ch.dissem.bitmessage.utils.UnixTime
|
import ch.dissem.bitmessage.utils.UnixTime
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
@ -39,9 +39,9 @@ class ConnectionIO(
|
|||||||
private val getState: () -> Connection.State,
|
private val getState: () -> Connection.State,
|
||||||
private val handleMessage: (MessagePayload) -> Unit
|
private val handleMessage: (MessagePayload) -> Unit
|
||||||
) {
|
) {
|
||||||
private val headerOut: ByteBuffer = ByteBuffer.allocate(24)
|
private val headerOut: ByteBuffer = ByteBuffer.allocate(HEADER_SIZE)
|
||||||
private var payloadOut: ByteBuffer? = null
|
private var payloadOut: ByteBuffer? = null
|
||||||
private var reader: V3MessageReader? = V3MessageReader()
|
private val reader = V3MessageReader()
|
||||||
internal val sendingQueue: Deque<MessagePayload> = ConcurrentLinkedDeque<MessagePayload>()
|
internal val sendingQueue: Deque<MessagePayload> = ConcurrentLinkedDeque<MessagePayload>()
|
||||||
|
|
||||||
internal var lastUpdate = System.currentTimeMillis()
|
internal var lastUpdate = System.currentTimeMillis()
|
||||||
@ -54,14 +54,13 @@ class ConnectionIO(
|
|||||||
headerOut.flip()
|
headerOut.flip()
|
||||||
}
|
}
|
||||||
|
|
||||||
val inBuffer: ByteBuffer
|
val inBuffer: ByteBuffer = reader.buffer
|
||||||
get() = reader?.getActiveBuffer() ?: throw NodeException("Node is disconnected")
|
|
||||||
|
|
||||||
fun updateWriter() {
|
fun updateWriter() {
|
||||||
if (!headerOut.hasRemaining() && !sendingQueue.isEmpty()) {
|
if (!headerOut.hasRemaining() && !sendingQueue.isEmpty()) {
|
||||||
headerOut.clear()
|
headerOut.clear()
|
||||||
val payload = sendingQueue.poll()
|
val payload = sendingQueue.poll()
|
||||||
payloadOut = NetworkMessage(payload).writeHeaderAndGetPayloadBuffer(headerOut)
|
payloadOut = NetworkMessage(payload).writer().writeHeaderAndGetPayloadBuffer(headerOut)
|
||||||
headerOut.flip()
|
headerOut.flip()
|
||||||
lastUpdate = System.currentTimeMillis()
|
lastUpdate = System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
@ -77,7 +76,7 @@ class ConnectionIO(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun updateReader() {
|
fun updateReader() {
|
||||||
reader?.let { reader ->
|
reader.let { reader ->
|
||||||
reader.update()
|
reader.update()
|
||||||
if (!reader.getMessages().isEmpty()) {
|
if (!reader.getMessages().isEmpty()) {
|
||||||
val iterator = reader.getMessages().iterator()
|
val iterator = reader.getMessages().iterator()
|
||||||
@ -95,11 +94,11 @@ class ConnectionIO(
|
|||||||
|
|
||||||
fun updateSyncStatus() {
|
fun updateSyncStatus() {
|
||||||
if (!isSyncFinished) {
|
if (!isSyncFinished) {
|
||||||
isSyncFinished = reader?.getMessages()?.isEmpty() ?: true && syncFinished(null)
|
isSyncFinished = reader.getMessages().isEmpty() && syncFinished(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun syncFinished(msg: NetworkMessage?): Boolean {
|
private fun syncFinished(msg: NetworkMessage?): Boolean {
|
||||||
if (mode != Connection.Mode.SYNC) {
|
if (mode != Connection.Mode.SYNC) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -126,10 +125,6 @@ class ConnectionIO(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun disconnect() {
|
fun disconnect() {
|
||||||
reader?.let {
|
|
||||||
it.cleanup()
|
|
||||||
reader = null
|
|
||||||
}
|
|
||||||
payloadOut = null
|
payloadOut = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,7 +145,7 @@ class ConnectionIO(
|
|||||||
|| headerOut.hasRemaining()
|
|| headerOut.hasRemaining()
|
||||||
|| payloadOut?.hasRemaining() ?: false
|
|| payloadOut?.hasRemaining() ?: false
|
||||||
|
|
||||||
fun nothingToSend() = sendingQueue.isEmpty()
|
private fun nothingToSend() = sendingQueue.isEmpty()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val LOG = LoggerFactory.getLogger(ConnectionIO::class.java)
|
val LOG = LoggerFactory.getLogger(ConnectionIO::class.java)
|
||||||
|
@ -41,7 +41,7 @@ class NetworkConnectionInitializer(
|
|||||||
|
|
||||||
fun start() {
|
fun start() {
|
||||||
if (mode == Connection.Mode.CLIENT || mode == Connection.Mode.SYNC) {
|
if (mode == Connection.Mode.CLIENT || mode == Connection.Mode.SYNC) {
|
||||||
send(Version(nonce = ctx.clientNonce, addrFrom = NetworkAddress.ANY, addrRecv = node, userAgent = ctx.userAgent))
|
send(Version(nonce = ctx.clientNonce, addrFrom = NetworkAddress.ANY, addrRecv = node, userAgent = ctx.preferences.userAgent))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,12 +77,12 @@ class NetworkConnectionInitializer(
|
|||||||
activateConnection()
|
activateConnection()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw NodeException("Received unsupported version " + version.version + ", disconnecting.")
|
throw NodeException("Received unsupported version ${version.version}, disconnecting.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun activateConnection() {
|
private fun activateConnection() {
|
||||||
LOG.info("Successfully established connection with node " + node)
|
LOG.info("Successfully established connection with node $node")
|
||||||
markActive(version.streams)
|
markActive(version.streams)
|
||||||
node.time = UnixTime.now
|
node.time = UnixTime.now
|
||||||
if (mode != Connection.Mode.SYNC) {
|
if (mode != Connection.Mode.SYNC) {
|
||||||
|
@ -34,6 +34,7 @@ import ch.dissem.bitmessage.utils.Property
|
|||||||
import ch.dissem.bitmessage.utils.ThreadFactoryBuilder.Companion.pool
|
import ch.dissem.bitmessage.utils.ThreadFactoryBuilder.Companion.pool
|
||||||
import ch.dissem.bitmessage.utils.UnixTime.now
|
import ch.dissem.bitmessage.utils.UnixTime.now
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.io.Closeable
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
@ -47,20 +48,22 @@ import java.util.concurrent.*
|
|||||||
/**
|
/**
|
||||||
* Network handler using java.nio, resulting in less threads.
|
* Network handler using java.nio, resulting in less threads.
|
||||||
*/
|
*/
|
||||||
class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
class NioNetworkHandler(private val magicNetworkNumber: Int = NETWORK_MAGIC_NUMBER) : NetworkHandler,
|
||||||
|
InternalContext.ContextHolder {
|
||||||
|
|
||||||
private val threadPool = Executors.newCachedThreadPool(
|
private val threadPool = Executors.newCachedThreadPool(
|
||||||
pool("network")
|
pool("network")
|
||||||
.lowPrio()
|
.lowPrio()
|
||||||
.daemon()
|
.daemon()
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
|
|
||||||
private lateinit var ctx: InternalContext
|
private lateinit var ctx: InternalContext
|
||||||
private var selector: Selector? = null
|
private var selector: Selector? = null
|
||||||
private var serverChannel: ServerSocketChannel? = null
|
private var serverChannel: ServerSocketChannel? = null
|
||||||
private val connectionQueue = ConcurrentLinkedQueue<NetworkAddress>()
|
private val connectionQueue = ConcurrentLinkedQueue<NetworkAddress>()
|
||||||
private val connections = ConcurrentHashMap<Connection, SelectionKey>()
|
private val connections: MutableMap<Connection, SelectionKey> = ConcurrentHashMap()
|
||||||
private val requestedObjects = ConcurrentHashMap<InventoryVector, Long>(10000)
|
private val requestedObjects: MutableMap<InventoryVector, Long> = ConcurrentHashMap(10000)
|
||||||
|
|
||||||
private var starter: Thread? = null
|
private var starter: Thread? = null
|
||||||
|
|
||||||
@ -72,9 +75,11 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
|||||||
return threadPool.submit(Callable<Void> {
|
return threadPool.submit(Callable<Void> {
|
||||||
SocketChannel.open(InetSocketAddress(server, port)).use { channel ->
|
SocketChannel.open(InetSocketAddress(server, port)).use { channel ->
|
||||||
channel.configureBlocking(false)
|
channel.configureBlocking(false)
|
||||||
val connection = Connection(ctx, SYNC,
|
val connection = Connection(
|
||||||
|
ctx, SYNC,
|
||||||
NetworkAddress.Builder().ip(server).port(port).stream(1).build(),
|
NetworkAddress.Builder().ip(server).port(port).stream(1).build(),
|
||||||
HashMap<InventoryVector, Long>(), timeoutInSeconds)
|
HashMap(), timeoutInSeconds
|
||||||
|
)
|
||||||
while (channel.isConnected && !connection.isSyncFinished) {
|
while (channel.isConnected && !connection.isSyncFinished) {
|
||||||
write(channel, connection.io)
|
write(channel, connection.io)
|
||||||
read(channel, connection.io)
|
read(channel, connection.io)
|
||||||
@ -90,7 +95,7 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
|||||||
SocketChannel.open(InetSocketAddress(server, port)).use { channel ->
|
SocketChannel.open(InetSocketAddress(server, port)).use { channel ->
|
||||||
channel.configureBlocking(true)
|
channel.configureBlocking(true)
|
||||||
val headerBuffer = ByteBuffer.allocate(HEADER_SIZE)
|
val headerBuffer = ByteBuffer.allocate(HEADER_SIZE)
|
||||||
val payloadBuffer = NetworkMessage(request).writeHeaderAndGetPayloadBuffer(headerBuffer)
|
val payloadBuffer = NetworkMessage(request).writer().writeHeaderAndGetPayloadBuffer(headerBuffer)
|
||||||
headerBuffer.flip()
|
headerBuffer.flip()
|
||||||
while (headerBuffer.hasRemaining()) {
|
while (headerBuffer.hasRemaining()) {
|
||||||
channel.write(headerBuffer)
|
channel.write(headerBuffer)
|
||||||
@ -101,7 +106,7 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
|||||||
|
|
||||||
val reader = V3MessageReader()
|
val reader = V3MessageReader()
|
||||||
while (channel.isConnected && reader.getMessages().isEmpty()) {
|
while (channel.isConnected && reader.getMessages().isEmpty()) {
|
||||||
if (channel.read(reader.getActiveBuffer()) > 0) {
|
if (channel.read(reader.buffer) > 0) {
|
||||||
reader.update()
|
reader.update()
|
||||||
} else {
|
} else {
|
||||||
throw NodeException("No response from node $server")
|
throw NodeException("No response from node $server")
|
||||||
@ -109,7 +114,7 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
|||||||
}
|
}
|
||||||
val networkMessage: NetworkMessage?
|
val networkMessage: NetworkMessage?
|
||||||
if (reader.getMessages().isEmpty()) {
|
if (reader.getMessages().isEmpty()) {
|
||||||
throw NodeException("No response from node " + server)
|
throw NodeException("No response from node $server")
|
||||||
} else {
|
} else {
|
||||||
networkMessage = reader.getMessages().first()
|
networkMessage = reader.getMessages().first()
|
||||||
}
|
}
|
||||||
@ -123,7 +128,7 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun start() {
|
override fun start() {
|
||||||
if (selector?.isOpen ?: false) {
|
if (selector?.isOpen == true) {
|
||||||
throw IllegalStateException("Network already running - you need to stop first.")
|
throw IllegalStateException("Network already running - you need to stop first.")
|
||||||
}
|
}
|
||||||
val selector = Selector.open()
|
val selector = Selector.open()
|
||||||
@ -133,7 +138,7 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
|||||||
|
|
||||||
starter = thread("connection manager") {
|
starter = thread("connection manager") {
|
||||||
while (selector.isOpen) {
|
while (selector.isOpen) {
|
||||||
var missing = NETWORK_MAGIC_NUMBER
|
var missing = magicNetworkNumber
|
||||||
for ((connection, _) in connections) {
|
for ((connection, _) in connections) {
|
||||||
if (connection.state == Connection.State.ACTIVE) {
|
if (connection.state == Connection.State.ACTIVE) {
|
||||||
missing--
|
missing--
|
||||||
@ -187,19 +192,19 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
|||||||
request(delayed)
|
request(delayed)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Thread.sleep(30000)
|
Thread.sleep(10000)
|
||||||
} catch (e: InterruptedException) {
|
} catch (e: InterruptedException) {
|
||||||
return@thread
|
return@thread
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
thread("selector worker", {
|
thread("selector worker") {
|
||||||
try {
|
try {
|
||||||
val serverChannel = ServerSocketChannel.open()
|
val serverChannel = ServerSocketChannel.open()
|
||||||
this.serverChannel = serverChannel
|
this.serverChannel = serverChannel
|
||||||
serverChannel.configureBlocking(false)
|
serverChannel.configureBlocking(false)
|
||||||
serverChannel.socket().bind(InetSocketAddress(ctx.port))
|
serverChannel.socket().bind(InetSocketAddress(ctx.preferences.port))
|
||||||
serverChannel.register(selector, OP_ACCEPT, null)
|
serverChannel.register(selector, OP_ACCEPT, null)
|
||||||
|
|
||||||
while (selector.isOpen) {
|
while (selector.isOpen) {
|
||||||
@ -216,7 +221,8 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
|||||||
try {
|
try {
|
||||||
val accepted = (key.channel() as ServerSocketChannel).accept()
|
val accepted = (key.channel() as ServerSocketChannel).accept()
|
||||||
accepted.configureBlocking(false)
|
accepted.configureBlocking(false)
|
||||||
val connection = Connection(ctx, SERVER,
|
val connection = Connection(
|
||||||
|
ctx, SERVER,
|
||||||
NetworkAddress(
|
NetworkAddress(
|
||||||
time = now,
|
time = now,
|
||||||
stream = 1L,
|
stream = 1L,
|
||||||
@ -224,10 +230,8 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
|||||||
),
|
),
|
||||||
requestedObjects, 0
|
requestedObjects, 0
|
||||||
)
|
)
|
||||||
connections.put(
|
connections[connection] =
|
||||||
connection,
|
|
||||||
accepted.register(selector, OP_READ or OP_WRITE, connection)
|
accepted.register(selector, OP_READ or OP_WRITE, connection)
|
||||||
)
|
|
||||||
} catch (e: AsynchronousCloseException) {
|
} catch (e: AsynchronousCloseException) {
|
||||||
LOG.trace(e.message)
|
LOG.trace(e.message)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
@ -255,19 +259,22 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
|||||||
if (key.isReadable) {
|
if (key.isReadable) {
|
||||||
read(channel, connection.io)
|
read(channel, connection.io)
|
||||||
}
|
}
|
||||||
if (connection.state == Connection.State.DISCONNECTED) {
|
when {
|
||||||
|
connection.state == Connection.State.DISCONNECTED -> {
|
||||||
key.interestOps(0)
|
key.interestOps(0)
|
||||||
channel.close()
|
channel.close()
|
||||||
} else if (connection.io.isWritePending) {
|
}
|
||||||
key.interestOps(OP_READ or OP_WRITE)
|
connection.io.isWritePending -> key.interestOps(OP_READ or OP_WRITE)
|
||||||
} else {
|
else -> key.interestOps(OP_READ)
|
||||||
key.interestOps(OP_READ)
|
|
||||||
}
|
}
|
||||||
} catch (e: CancelledKeyException) {
|
} catch (e: CancelledKeyException) {
|
||||||
|
LOG.debug("${e.message}: ${connection.node}", e)
|
||||||
connection.disconnect()
|
connection.disconnect()
|
||||||
} catch (e: NodeException) {
|
} catch (e: NodeException) {
|
||||||
|
LOG.debug("${e.message}: ${connection.node}", e)
|
||||||
connection.disconnect()
|
connection.disconnect()
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
|
LOG.debug("${e.message}: ${connection.node}", e)
|
||||||
connection.disconnect()
|
connection.disconnect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -281,7 +288,8 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
|||||||
if (selectionKey.isValid
|
if (selectionKey.isValid
|
||||||
&& selectionKey.interestOps() and OP_WRITE == 0
|
&& selectionKey.interestOps() and OP_WRITE == 0
|
||||||
&& selectionKey.interestOps() and OP_CONNECT == 0
|
&& selectionKey.interestOps() and OP_CONNECT == 0
|
||||||
&& !connection.nothingToSend) {
|
&& !connection.nothingToSend
|
||||||
|
) {
|
||||||
selectionKey.interestOps(OP_READ or OP_WRITE)
|
selectionKey.interestOps(OP_READ or OP_WRITE)
|
||||||
}
|
}
|
||||||
} catch (x: CancelledKeyException) {
|
} catch (x: CancelledKeyException) {
|
||||||
@ -297,10 +305,11 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
|||||||
channel.configureBlocking(false)
|
channel.configureBlocking(false)
|
||||||
channel.connect(InetSocketAddress(address.toInetAddress(), address.port))
|
channel.connect(InetSocketAddress(address.toInetAddress(), address.port))
|
||||||
val connection = Connection(ctx, CLIENT, address, requestedObjects, 0)
|
val connection = Connection(ctx, CLIENT, address, requestedObjects, 0)
|
||||||
connections.put(
|
|
||||||
connection,
|
connections[connection] = channel.register(selector, OP_CONNECT, connection)
|
||||||
channel.register(selector, OP_CONNECT, connection)
|
|
||||||
)
|
LOG.debug("Connection registered to $address")
|
||||||
|
|
||||||
} catch (ignore: NoRouteToHostException) {
|
} catch (ignore: NoRouteToHostException) {
|
||||||
// We'll try to connect to many offline nodes, so
|
// We'll try to connect to many offline nodes, so
|
||||||
// this is expected to happen quite a lot.
|
// this is expected to happen quite a lot.
|
||||||
@ -312,7 +321,11 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
|||||||
LOG.error(e.message, e)
|
LOG.error(e.message, e)
|
||||||
}
|
}
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
LOG.error(e.message, e)
|
if (e.message == "Network is unreachable") {
|
||||||
|
LOG.debug("Network is unreachable: $address")
|
||||||
|
} else {
|
||||||
|
LOG.error("${e.message}: $address", e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -326,7 +339,7 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
|||||||
// isn't nice though.
|
// isn't nice though.
|
||||||
LOG.error(e.message, e)
|
LOG.error(e.message, e)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun thread(threadName: String, runnable: () -> Unit): Thread {
|
private fun thread(threadName: String, runnable: () -> Unit): Thread {
|
||||||
@ -338,16 +351,24 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun stop() {
|
override fun stop() {
|
||||||
serverChannel?.socket()?.close()
|
tryClose(serverChannel?.socket())
|
||||||
selector?.close()
|
tryClose(selector)
|
||||||
for (selectionKey in connections.values) {
|
for (selectionKey in connections.values) {
|
||||||
selectionKey.channel().close()
|
tryClose(selectionKey.channel())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun tryClose(item: Closeable?) {
|
||||||
|
try {
|
||||||
|
item?.close()
|
||||||
|
} catch (e: IOException) {
|
||||||
|
LOG.debug(e.message, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun offer(iv: InventoryVector) {
|
override fun offer(iv: InventoryVector) {
|
||||||
val targetConnections = connections.keys.filter { it.state == Connection.State.ACTIVE && !it.knowsOf(iv) }
|
val targetConnections = connections.keys.filter { it.state == Connection.State.ACTIVE && !it.knowsOf(iv) }
|
||||||
selectRandom(NETWORK_MAGIC_NUMBER, targetConnections).forEach { it.offer(iv) }
|
selectRandom(magicNetworkNumber, targetConnections).forEach { it.offer(iv) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun request(inventoryVectors: MutableCollection<InventoryVector>) {
|
override fun request(inventoryVectors: MutableCollection<InventoryVector>) {
|
||||||
@ -363,7 +384,7 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
|||||||
val distribution = HashMap<Connection, MutableList<InventoryVector>>()
|
val distribution = HashMap<Connection, MutableList<InventoryVector>>()
|
||||||
for ((connection, _) in connections) {
|
for ((connection, _) in connections) {
|
||||||
if (connection.state == Connection.State.ACTIVE) {
|
if (connection.state == Connection.State.ACTIVE) {
|
||||||
distribution.put(connection, mutableListOf<InventoryVector>())
|
distribution[connection] = mutableListOf()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (distribution.isEmpty()) {
|
if (distribution.isEmpty()) {
|
||||||
@ -382,7 +403,8 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (connection.knowsOf(next) && !connection.requested(next)) {
|
if (connection.knowsOf(next) && !connection.requested(next)) {
|
||||||
val ivs = distribution[connection] ?: throw IllegalStateException("distribution not available for $connection")
|
val ivs = distribution[connection]
|
||||||
|
?: throw IllegalStateException("distribution not available for $connection")
|
||||||
if (ivs.size == GetData.MAX_INVENTORY_SIZE) {
|
if (ivs.size == GetData.MAX_INVENTORY_SIZE) {
|
||||||
connection.send(GetData(ivs))
|
connection.send(GetData(ivs))
|
||||||
ivs.clear()
|
ivs.clear()
|
||||||
@ -406,7 +428,8 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (connection in distribution.keys) {
|
for (connection in distribution.keys) {
|
||||||
val ivs = distribution[connection] ?: throw IllegalStateException("distribution not available for $connection")
|
val ivs =
|
||||||
|
distribution[connection] ?: throw IllegalStateException("distribution not available for $connection")
|
||||||
if (!ivs.isEmpty()) {
|
if (!ivs.isEmpty()) {
|
||||||
connection.send(GetData(ivs))
|
connection.send(GetData(ivs))
|
||||||
}
|
}
|
||||||
@ -434,12 +457,16 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
|||||||
for (stream in streams) {
|
for (stream in streams) {
|
||||||
val incoming = incomingConnections[stream] ?: 0
|
val incoming = incomingConnections[stream] ?: 0
|
||||||
val outgoing = outgoingConnections[stream] ?: 0
|
val outgoing = outgoingConnections[stream] ?: 0
|
||||||
streamProperties.add(Property("stream " + stream, Property("nodes", incoming + outgoing),
|
streamProperties.add(
|
||||||
|
Property(
|
||||||
|
"stream $stream", Property("nodes", incoming + outgoing),
|
||||||
Property("incoming", incoming),
|
Property("incoming", incoming),
|
||||||
Property("outgoing", outgoing)
|
Property("outgoing", outgoing)
|
||||||
))
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return Property("network",
|
return Property(
|
||||||
|
"network",
|
||||||
Property("connectionManager", if (isRunning) "running" else "stopped"),
|
Property("connectionManager", if (isRunning) "running" else "stopped"),
|
||||||
Property("connections", streamProperties),
|
Property("connections", streamProperties),
|
||||||
Property("requestedObjects", requestedObjects.size)
|
Property("requestedObjects", requestedObjects.size)
|
||||||
@ -453,8 +480,8 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val LOG = LoggerFactory.getLogger(NioNetworkHandler::class.java)
|
private val LOG = LoggerFactory.getLogger(NioNetworkHandler::class.java)
|
||||||
private val REQUESTED_OBJECTS_MAX_TIME = (2 * 60000).toLong() // 2 minutes in ms
|
private const val REQUESTED_OBJECTS_MAX_TIME = 2 * 60000L // 2 minutes in ms
|
||||||
private val DELAYED = java.lang.Long.MIN_VALUE
|
private const val DELAYED = java.lang.Long.MIN_VALUE
|
||||||
|
|
||||||
private fun write(channel: SocketChannel, connection: ConnectionIO) {
|
private fun write(channel: SocketChannel, connection: ConnectionIO) {
|
||||||
writeBuffer(connection.outBuffers, channel)
|
writeBuffer(connection.outBuffers, channel)
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user