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:
|
||||
|
||||
# IntelliJ
|
||||
/out/
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
36
build.gradle
36
build.gradle
@ -1,5 +1,5 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.1.4-3'
|
||||
ext.kotlin_version = '1.2.71'
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
@ -8,17 +8,17 @@ buildscript {
|
||||
}
|
||||
}
|
||||
plugins {
|
||||
id 'com.github.ben-manes.versions' version '0.15.0'
|
||||
id "io.spring.dependency-management" version "1.0.3.RELEASE"
|
||||
id 'com.github.ben-manes.versions' version '0.17.0'
|
||||
id "io.spring.dependency-management" version "1.0.4.RELEASE"
|
||||
}
|
||||
|
||||
subprojects {
|
||||
apply plugin: 'io.spring.dependency-management'
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'maven'
|
||||
apply plugin: 'signing'
|
||||
apply plugin: 'jacoco'
|
||||
apply plugin: 'gitflow-version'
|
||||
apply plugin: 'io.spring.dependency-management'
|
||||
apply plugin: 'com.github.ben-manes.versions'
|
||||
|
||||
sourceCompatibility = 1.7
|
||||
@ -31,8 +31,8 @@ subprojects {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7"
|
||||
compile "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7"
|
||||
implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
}
|
||||
|
||||
test {
|
||||
@ -130,7 +130,7 @@ subprojects {
|
||||
dependencyManagement {
|
||||
dependencies {
|
||||
dependencySet(group: 'org.jetbrains.kotlin', version: "$kotlin_version") {
|
||||
entry 'kotlin-stdlib-jre7'
|
||||
entry 'kotlin-stdlib-jdk7'
|
||||
entry 'kotlin-reflect'
|
||||
}
|
||||
dependencySet(group: 'org.slf4j', version: '1.7.25') {
|
||||
@ -138,20 +138,22 @@ subprojects {
|
||||
entry 'slf4j-simple'
|
||||
}
|
||||
|
||||
dependency 'ch.dissem.msgpack:msgpack:2.0.0'
|
||||
dependency 'org.bouncycastle:bcprov-jdk15on:1.57'
|
||||
dependency 'com.madgag.spongycastle:prov:1.56.0.0'
|
||||
dependency 'org.apache.commons:commons-lang3:3.6'
|
||||
dependency 'org.flywaydb:flyway-core:4.2.0'
|
||||
dependency 'com.beust:klaxon:0.31'
|
||||
dependency 'ch.dissem.msgpack:msgpack:2.0.1'
|
||||
dependency 'org.bouncycastle:bcprov-jdk15on:1.60'
|
||||
dependency 'com.madgag.spongycastle:prov:1.58.0.0'
|
||||
dependency 'org.apache.commons:commons-text:1.5'
|
||||
dependency 'org.flywaydb:flyway-core:5.2.0'
|
||||
dependency 'com.beust:klaxon:3.0.8'
|
||||
|
||||
dependency 'args4j:args4j:2.33'
|
||||
dependency 'org.ini4j:ini4j:0.5.4'
|
||||
dependency 'com.h2database:h2:1.4.196'
|
||||
dependency 'com.h2database:h2:1.4.197'
|
||||
|
||||
dependency 'junit:junit:4.12'
|
||||
dependency 'org.hamcrest:hamcrest-library:1.3'
|
||||
dependency 'com.nhaarman:mockito-kotlin:1.5.0'
|
||||
dependency 'org.hamcrest:java-hamcrest:2.0.0.0'
|
||||
dependency 'com.nhaarman:mockito-kotlin:1.6.0'
|
||||
|
||||
dependency 'org.junit.jupiter:junit-jupiter-api:5.3.1'
|
||||
dependency 'org.junit.jupiter:junit-jupiter-engine:5.3.1'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,10 +25,10 @@ artifacts {
|
||||
|
||||
dependencies {
|
||||
compile 'org.slf4j:slf4j-api'
|
||||
compile 'ch.dissem.msgpack:msgpack:1.0.0'
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile 'org.hamcrest:hamcrest-library:1.3'
|
||||
testCompile 'com.nhaarman:mockito-kotlin:1.5.0'
|
||||
compile 'ch.dissem.msgpack:msgpack'
|
||||
testCompile 'com.nhaarman:mockito-kotlin'
|
||||
testCompile 'org.junit.jupiter:junit-jupiter-api'
|
||||
testRuntime 'org.junit.jupiter:junit-jupiter-engine'
|
||||
testCompile project(':cryptography-bc')
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
package ch.dissem.bitmessage
|
||||
|
||||
import ch.dissem.bitmessage.BitmessageContext.Companion.version
|
||||
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES
|
||||
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||
@ -33,7 +34,6 @@ import ch.dissem.bitmessage.exception.DecryptionFailedException
|
||||
import ch.dissem.bitmessage.factory.Factory
|
||||
import ch.dissem.bitmessage.ports.*
|
||||
import ch.dissem.bitmessage.utils.Property
|
||||
import ch.dissem.bitmessage.utils.UnixTime.HOUR
|
||||
import ch.dissem.bitmessage.utils.UnixTime.MINUTE
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.net.InetAddress
|
||||
@ -58,57 +58,7 @@ import kotlin.properties.Delegates
|
||||
*
|
||||
* The port defaults to 8444 (the default Bitmessage port)
|
||||
*/
|
||||
class BitmessageContext(
|
||||
cryptography: Cryptography,
|
||||
inventory: Inventory,
|
||||
nodeRegistry: NodeRegistry,
|
||||
networkHandler: NetworkHandler,
|
||||
addressRepository: AddressRepository,
|
||||
messageRepository: MessageRepository,
|
||||
proofOfWorkRepository: ProofOfWorkRepository,
|
||||
proofOfWorkEngine: ProofOfWorkEngine = MultiThreadedPOWEngine(),
|
||||
customCommandHandler: CustomCommandHandler = object : CustomCommandHandler {
|
||||
override fun handle(request: CustomMessage): MessagePayload? {
|
||||
BitmessageContext.LOG.debug("Received custom request, but no custom command handler configured.")
|
||||
return null
|
||||
}
|
||||
},
|
||||
listener: Listener,
|
||||
labeler: Labeler = DefaultLabeler(),
|
||||
userAgent: String? = null,
|
||||
port: Int = 8444,
|
||||
connectionTTL: Long = 30 * MINUTE,
|
||||
connectionLimit: Int = 150,
|
||||
sendPubkeyOnIdentityCreation: Boolean = true,
|
||||
doMissingProofOfWorkDelayInSeconds: Int = 30
|
||||
) {
|
||||
|
||||
private constructor(builder: BitmessageContext.Builder) : this(
|
||||
builder.cryptography,
|
||||
builder.inventory,
|
||||
builder.nodeRegistry,
|
||||
builder.networkHandler,
|
||||
builder.addressRepo,
|
||||
builder.messageRepo,
|
||||
builder.proofOfWorkRepository,
|
||||
builder.proofOfWorkEngine ?: MultiThreadedPOWEngine(),
|
||||
builder.customCommandHandler ?: object : CustomCommandHandler {
|
||||
override fun handle(request: CustomMessage): MessagePayload? {
|
||||
BitmessageContext.LOG.debug("Received custom request, but no custom command handler configured.")
|
||||
return null
|
||||
}
|
||||
},
|
||||
builder.listener,
|
||||
builder.labeler ?: DefaultLabeler(),
|
||||
builder.userAgent,
|
||||
builder.port,
|
||||
builder.connectionTTL,
|
||||
builder.connectionLimit,
|
||||
builder.sendPubkeyOnIdentityCreation,
|
||||
builder.doMissingProofOfWorkDelay
|
||||
)
|
||||
|
||||
private val sendPubkeyOnIdentityCreation: Boolean
|
||||
class BitmessageContext private constructor(builder: BitmessageContext.Builder) {
|
||||
|
||||
/**
|
||||
* The [InternalContext] - normally you wouldn't need it,
|
||||
@ -123,6 +73,9 @@ class BitmessageContext(
|
||||
val addresses: AddressRepository
|
||||
@JvmName("addresses") get
|
||||
|
||||
val labels: LabelRepository
|
||||
@JvmName("labels") get
|
||||
|
||||
val messages: MessageRepository
|
||||
@JvmName("messages") get
|
||||
|
||||
@ -135,7 +88,7 @@ class BitmessageContext(
|
||||
*features
|
||||
))
|
||||
internals.addressRepository.save(identity)
|
||||
if (sendPubkeyOnIdentityCreation) {
|
||||
if (internals.preferences.sendPubkeyOnIdentityCreation) {
|
||||
internals.sendPubkey(identity, identity.stream)
|
||||
}
|
||||
return identity
|
||||
@ -262,9 +215,8 @@ class BitmessageContext(
|
||||
* @param request the request
|
||||
* @return the response
|
||||
*/
|
||||
fun send(server: InetAddress, port: Int, request: CustomMessage): CustomMessage {
|
||||
return internals.networkHandler.send(server, port, request)
|
||||
}
|
||||
fun send(server: InetAddress, port: Int, request: CustomMessage): CustomMessage =
|
||||
internals.networkHandler.send(server, port, request)
|
||||
|
||||
/**
|
||||
* Removes expired objects from the inventory. You should call this method regularly,
|
||||
@ -272,6 +224,7 @@ class BitmessageContext(
|
||||
*/
|
||||
fun cleanup() {
|
||||
internals.inventory.cleanup()
|
||||
internals.nodeRegistry.cleanup()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -326,7 +279,7 @@ class BitmessageContext(
|
||||
|
||||
fun status(): Property {
|
||||
return Property("status",
|
||||
Property("user agent", internals.userAgent),
|
||||
Property("user agent", internals.preferences.userAgent),
|
||||
internals.networkHandler.getNetworkStatus(),
|
||||
Property("unacknowledged", internals.messageRepository.findMessagesToResend().size)
|
||||
)
|
||||
@ -343,29 +296,23 @@ class BitmessageContext(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Kotlin users: you might want to use [BitmessageContext.build] instead.
|
||||
*/
|
||||
class Builder {
|
||||
internal var port = 8444
|
||||
internal var inventory by Delegates.notNull<Inventory>()
|
||||
internal var nodeRegistry by Delegates.notNull<NodeRegistry>()
|
||||
internal var networkHandler by Delegates.notNull<NetworkHandler>()
|
||||
internal var addressRepo by Delegates.notNull<AddressRepository>()
|
||||
internal var messageRepo by Delegates.notNull<MessageRepository>()
|
||||
internal var proofOfWorkRepository by Delegates.notNull<ProofOfWorkRepository>()
|
||||
internal var proofOfWorkEngine: ProofOfWorkEngine? = null
|
||||
internal var cryptography by Delegates.notNull<Cryptography>()
|
||||
internal var customCommandHandler: CustomCommandHandler? = null
|
||||
internal var labeler: Labeler? = null
|
||||
internal var userAgent: String? = null
|
||||
internal var listener by Delegates.notNull<Listener>()
|
||||
internal var connectionLimit = 150
|
||||
internal var connectionTTL = 30 * MINUTE
|
||||
internal var sendPubkeyOnIdentityCreation = true
|
||||
internal var doMissingProofOfWorkDelay = 30
|
||||
|
||||
fun port(port: Int): Builder {
|
||||
this.port = port
|
||||
return this
|
||||
}
|
||||
var inventory by Delegates.notNull<Inventory>()
|
||||
var nodeRegistry by Delegates.notNull<NodeRegistry>()
|
||||
var networkHandler by Delegates.notNull<NetworkHandler>()
|
||||
var addressRepo by Delegates.notNull<AddressRepository>()
|
||||
var labelRepo by Delegates.notNull<LabelRepository>()
|
||||
var messageRepo by Delegates.notNull<MessageRepository>()
|
||||
var proofOfWorkRepo by Delegates.notNull<ProofOfWorkRepository>()
|
||||
var proofOfWorkEngine: ProofOfWorkEngine? = null
|
||||
var cryptography by Delegates.notNull<Cryptography>()
|
||||
var customCommandHandler: CustomCommandHandler? = null
|
||||
var labeler: Labeler? = null
|
||||
var listener by Delegates.notNull<Listener>()
|
||||
val preferences = Preferences()
|
||||
|
||||
fun inventory(inventory: Inventory): Builder {
|
||||
this.inventory = inventory
|
||||
@ -387,13 +334,18 @@ class BitmessageContext(
|
||||
return this
|
||||
}
|
||||
|
||||
fun labelRepo(labelRepo: LabelRepository): Builder {
|
||||
this.labelRepo = labelRepo
|
||||
return this
|
||||
}
|
||||
|
||||
fun messageRepo(messageRepo: MessageRepository): Builder {
|
||||
this.messageRepo = messageRepo
|
||||
return this
|
||||
}
|
||||
|
||||
fun powRepo(proofOfWorkRepository: ProofOfWorkRepository): Builder {
|
||||
this.proofOfWorkRepository = proofOfWorkRepository
|
||||
this.proofOfWorkRepo = proofOfWorkRepository
|
||||
return this
|
||||
}
|
||||
|
||||
@ -422,7 +374,7 @@ class BitmessageContext(
|
||||
return this
|
||||
}
|
||||
|
||||
@JvmName("kotlinListener")
|
||||
@JvmSynthetic
|
||||
fun listener(listener: (Plaintext) -> Unit): Builder {
|
||||
this.listener = object : Listener {
|
||||
override fun receive(plaintext: Plaintext) {
|
||||
@ -432,63 +384,41 @@ class BitmessageContext(
|
||||
return this
|
||||
}
|
||||
|
||||
fun connectionLimit(connectionLimit: Int): Builder {
|
||||
this.connectionLimit = connectionLimit
|
||||
return this
|
||||
}
|
||||
|
||||
fun connectionTTL(hours: Int): Builder {
|
||||
this.connectionTTL = hours * HOUR
|
||||
return this
|
||||
}
|
||||
|
||||
fun doMissingProofOfWorkDelay(seconds: Int) {
|
||||
this.doMissingProofOfWorkDelay = seconds
|
||||
}
|
||||
|
||||
/**
|
||||
* By default a client will send the public key when an identity is being created. On weaker devices
|
||||
* this behaviour might not be desirable.
|
||||
*/
|
||||
fun doNotSendPubkeyOnIdentityCreation(): Builder {
|
||||
this.sendPubkeyOnIdentityCreation = false
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(): BitmessageContext {
|
||||
return BitmessageContext(this)
|
||||
}
|
||||
fun build() = BitmessageContext(this)
|
||||
}
|
||||
|
||||
|
||||
init {
|
||||
this.labeler = labeler
|
||||
this.labeler = builder.labeler ?: DefaultLabeler()
|
||||
this.internals = InternalContext(
|
||||
cryptography,
|
||||
inventory,
|
||||
nodeRegistry,
|
||||
networkHandler,
|
||||
addressRepository,
|
||||
messageRepository,
|
||||
proofOfWorkRepository,
|
||||
proofOfWorkEngine,
|
||||
customCommandHandler,
|
||||
listener,
|
||||
builder.cryptography,
|
||||
builder.inventory,
|
||||
builder.nodeRegistry,
|
||||
builder.networkHandler,
|
||||
builder.addressRepo,
|
||||
builder.labelRepo,
|
||||
builder.messageRepo,
|
||||
builder.proofOfWorkRepo,
|
||||
builder.proofOfWorkEngine ?: MultiThreadedPOWEngine(),
|
||||
builder.customCommandHandler ?: object : CustomCommandHandler {
|
||||
override fun handle(request: CustomMessage): MessagePayload? {
|
||||
BitmessageContext.LOG.debug("Received custom request, but no custom command handler configured.")
|
||||
return null
|
||||
}
|
||||
},
|
||||
builder.listener,
|
||||
labeler,
|
||||
userAgent?.let { "/$it/Jabit:$version/" } ?: "/Jabit:$version/",
|
||||
port,
|
||||
connectionTTL,
|
||||
connectionLimit
|
||||
builder.preferences
|
||||
)
|
||||
this.addresses = addressRepository
|
||||
this.messages = messageRepository
|
||||
this.sendPubkeyOnIdentityCreation = sendPubkeyOnIdentityCreation
|
||||
(listener as? Listener.WithContext)?.setContext(this)
|
||||
internals.proofOfWorkService.doMissingProofOfWork(doMissingProofOfWorkDelayInSeconds * 1000L)
|
||||
this.addresses = builder.addressRepo
|
||||
this.labels = builder.labelRepo
|
||||
this.messages = builder.messageRepo
|
||||
(builder.listener as? Listener.WithContext)?.setContext(this)
|
||||
internals.proofOfWorkService.doMissingProofOfWork(builder.preferences.doMissingProofOfWorkDelayInSeconds * 1000L)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmField val CURRENT_VERSION = 3
|
||||
@JvmField
|
||||
val CURRENT_VERSION = 3
|
||||
private val LOG = LoggerFactory.getLogger(BitmessageContext::class.java)
|
||||
|
||||
val version: String by lazy {
|
||||
@ -496,5 +426,40 @@ class BitmessageContext(
|
||||
}
|
||||
@JvmStatic get
|
||||
|
||||
@JvmSynthetic
|
||||
inline fun build(block: Builder.() -> Unit): BitmessageContext {
|
||||
val builder = Builder()
|
||||
block(builder)
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class Preferences {
|
||||
var port = 8444
|
||||
/**
|
||||
* Defaults to "/Jabit:<version>/", and whatever you set will be inserted into "/<your user agent>/Jabit:<version>/"
|
||||
*/
|
||||
var userAgent = "/Jabit:$version/"
|
||||
set(value) {
|
||||
field = "/$value/Jabit:$version/"
|
||||
}
|
||||
/**
|
||||
* Time to live for any connection
|
||||
*/
|
||||
var connectionTTL = 30 * MINUTE
|
||||
/**
|
||||
* Maximum number of connections. Values below 8 would probably result in erratic behaviour, so you shouldn't do that.
|
||||
*/
|
||||
var connectionLimit = 150
|
||||
/**
|
||||
* By default a client will send the public key when an identity is being created. On weaker devices
|
||||
* this behaviour might not be desirable.
|
||||
*/
|
||||
var sendPubkeyOnIdentityCreation = true
|
||||
/**
|
||||
* Delay in seconds before outstandinng proof of work is calculated.
|
||||
*/
|
||||
var doMissingProofOfWorkDelayInSeconds = 30
|
||||
}
|
||||
|
@ -40,22 +40,19 @@ import java.util.concurrent.Executors
|
||||
*/
|
||||
class InternalContext(
|
||||
val cryptography: Cryptography,
|
||||
val inventory: ch.dissem.bitmessage.ports.Inventory,
|
||||
val inventory: Inventory,
|
||||
val nodeRegistry: NodeRegistry,
|
||||
val networkHandler: NetworkHandler,
|
||||
val addressRepository: AddressRepository,
|
||||
val messageRepository: ch.dissem.bitmessage.ports.MessageRepository,
|
||||
val labelRepository: LabelRepository,
|
||||
val messageRepository: MessageRepository,
|
||||
val proofOfWorkRepository: ProofOfWorkRepository,
|
||||
val proofOfWorkEngine: ProofOfWorkEngine,
|
||||
val customCommandHandler: CustomCommandHandler,
|
||||
listener: BitmessageContext.Listener,
|
||||
val labeler: Labeler,
|
||||
|
||||
val userAgent: String,
|
||||
|
||||
val port: Int,
|
||||
val connectionTTL: Long,
|
||||
val connectionLimit: Int
|
||||
val preferences: Preferences
|
||||
) {
|
||||
|
||||
private val threadPool = Executors.newCachedThreadPool()
|
||||
@ -220,7 +217,9 @@ class InternalContext(
|
||||
companion object {
|
||||
private val LOG = LoggerFactory.getLogger(InternalContext::class.java)
|
||||
|
||||
@JvmField val NETWORK_NONCE_TRIALS_PER_BYTE: Long = 1000
|
||||
@JvmField val NETWORK_EXTRA_BYTES: Long = 1000
|
||||
@JvmField
|
||||
val NETWORK_NONCE_TRIALS_PER_BYTE: Long = 1000
|
||||
@JvmField
|
||||
val NETWORK_EXTRA_BYTES: Long = 1000
|
||||
}
|
||||
}
|
||||
|
@ -25,19 +25,28 @@ import java.nio.ByteBuffer
|
||||
* The 'addr' command holds a list of known active Bitmessage nodes.
|
||||
*/
|
||||
data class Addr constructor(val addresses: List<NetworkAddress>) : MessagePayload {
|
||||
|
||||
override val command: MessagePayload.Command = MessagePayload.Command.ADDR
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
Encode.varInt(addresses.size, out)
|
||||
for (address in addresses) {
|
||||
address.write(out)
|
||||
}
|
||||
}
|
||||
override fun writer(): StreamableWriter = Writer(this)
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
Encode.varInt(addresses.size, buffer)
|
||||
for (address in addresses) {
|
||||
address.write(buffer)
|
||||
private class Writer(
|
||||
private val item: Addr
|
||||
) : StreamableWriter {
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
Encode.varInt(item.addresses.size, out)
|
||||
for (address in item.addresses) {
|
||||
address.writer().write(out)
|
||||
}
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
Encode.varInt(item.addresses.size, buffer)
|
||||
for (address in item.addresses) {
|
||||
address.writer().write(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -116,15 +116,15 @@ class BitmessageAddress : Serializable {
|
||||
constructor(address: String) {
|
||||
this.address = address
|
||||
val bytes = Base58.decode(address.substring(3))
|
||||
val `in` = ByteArrayInputStream(bytes)
|
||||
val input = ByteArrayInputStream(bytes)
|
||||
val counter = AccessCounter()
|
||||
this.version = varInt(`in`, counter)
|
||||
this.stream = varInt(`in`, counter)
|
||||
this.ripe = Bytes.expand(bytes(`in`, bytes.size - counter.length() - 4), 20)
|
||||
this.version = varInt(input, counter)
|
||||
this.stream = varInt(input, counter)
|
||||
this.ripe = Bytes.expand(bytes(input, bytes.size - counter.length() - 4), 20)
|
||||
|
||||
// test checksum
|
||||
var checksum = cryptography().doubleSha512(bytes, bytes.size - 4)
|
||||
val expectedChecksum = bytes(`in`, 4)
|
||||
val expectedChecksum = bytes(input, 4)
|
||||
for (i in 0..3) {
|
||||
if (expectedChecksum[i] != checksum[i])
|
||||
throw IllegalArgumentException("Checksum of address failed")
|
||||
|
@ -33,54 +33,54 @@ open class CustomMessage(val customCommand: String, private val data: ByteArray?
|
||||
|
||||
override val command: MessagePayload.Command = MessagePayload.Command.CUSTOM
|
||||
|
||||
val isError: Boolean
|
||||
val isError = COMMAND_ERROR == customCommand
|
||||
|
||||
fun getData(): ByteArray {
|
||||
if (data != null) {
|
||||
return data
|
||||
} else {
|
||||
return data ?: {
|
||||
val out = ByteArrayOutputStream()
|
||||
write(out)
|
||||
return out.toByteArray()
|
||||
}
|
||||
writer().write(out)
|
||||
out.toByteArray()
|
||||
}.invoke()
|
||||
}
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
if (data != null) {
|
||||
Encode.varString(customCommand, out)
|
||||
out.write(data)
|
||||
} else {
|
||||
throw ApplicationException("Tried to write custom message without data. "
|
||||
+ "Programmer: did you forget to override #write()?")
|
||||
}
|
||||
}
|
||||
override fun writer(): StreamableWriter = Writer(this)
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
if (data != null) {
|
||||
Encode.varString(customCommand, buffer)
|
||||
buffer.put(data)
|
||||
} else {
|
||||
throw ApplicationException("Tried to write custom message without data. "
|
||||
+ "Programmer: did you forget to override #write()?")
|
||||
protected open class Writer(
|
||||
private val item: CustomMessage
|
||||
) : StreamableWriter {
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
if (item.data != null) {
|
||||
Encode.varString(item.customCommand, out)
|
||||
out.write(item.data)
|
||||
} else {
|
||||
throw ApplicationException("Tried to write custom message without data. "
|
||||
+ "Programmer: did you forget to override #write()?")
|
||||
}
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
if (item.data != null) {
|
||||
Encode.varString(item.customCommand, buffer)
|
||||
buffer.put(item.data)
|
||||
} else {
|
||||
throw ApplicationException("Tried to write custom message without data. "
|
||||
+ "Programmer: did you forget to override #write()?")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
val COMMAND_ERROR = "ERROR"
|
||||
|
||||
@JvmStatic
|
||||
fun read(`in`: InputStream, length: Int): CustomMessage {
|
||||
fun read(input: InputStream, length: Int): CustomMessage {
|
||||
val counter = AccessCounter()
|
||||
return CustomMessage(varString(`in`, counter), bytes(`in`, length - counter.length()))
|
||||
return CustomMessage(varString(input, counter), bytes(input, length - counter.length()))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun error(message: String): CustomMessage {
|
||||
return CustomMessage(COMMAND_ERROR, message.toByteArray(charset("UTF-8")))
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
this.isError = COMMAND_ERROR == customCommand
|
||||
fun error(message: String) = CustomMessage(COMMAND_ERROR, message.toByteArray(charset("UTF-8")))
|
||||
}
|
||||
}
|
||||
|
@ -17,9 +17,6 @@
|
||||
package ch.dissem.bitmessage.entity
|
||||
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
||||
import ch.dissem.bitmessage.utils.Encode
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
/**
|
||||
* The 'getdata' command is used to request objects from a node.
|
||||
@ -28,21 +25,14 @@ class GetData constructor(var inventory: List<InventoryVector>) : MessagePayload
|
||||
|
||||
override val command: MessagePayload.Command = MessagePayload.Command.GETDATA
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
Encode.varInt(inventory.size, out)
|
||||
for (iv in inventory) {
|
||||
iv.write(out)
|
||||
}
|
||||
}
|
||||
override fun writer(): StreamableWriter = Writer(this)
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
Encode.varInt(inventory.size, buffer)
|
||||
for (iv in inventory) {
|
||||
iv.write(buffer)
|
||||
}
|
||||
}
|
||||
private class Writer(
|
||||
item: GetData
|
||||
) : InventoryWriter(item.inventory)
|
||||
|
||||
companion object {
|
||||
@JvmField val MAX_INVENTORY_SIZE = 50000
|
||||
@JvmField
|
||||
val MAX_INVENTORY_SIZE = 50000
|
||||
}
|
||||
}
|
||||
|
@ -17,9 +17,6 @@
|
||||
package ch.dissem.bitmessage.entity
|
||||
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
||||
import ch.dissem.bitmessage.utils.Encode
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
/**
|
||||
* The 'inv' command holds up to 50000 inventory vectors, i.e. hashes of inventory items.
|
||||
@ -28,17 +25,9 @@ class Inv constructor(val inventory: List<InventoryVector>) : MessagePayload {
|
||||
|
||||
override val command: MessagePayload.Command = MessagePayload.Command.INV
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
Encode.varInt(inventory.size, out)
|
||||
for (iv in inventory) {
|
||||
iv.write(out)
|
||||
}
|
||||
}
|
||||
override fun writer(): StreamableWriter = Writer(this)
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
Encode.varInt(inventory.size, buffer)
|
||||
for (iv in inventory) {
|
||||
iv.write(buffer)
|
||||
}
|
||||
}
|
||||
private class Writer(
|
||||
item: Inv
|
||||
) : InventoryWriter(item.inventory)
|
||||
}
|
||||
|
@ -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.Singleton.cryptography
|
||||
import java.io.IOException
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
@ -32,87 +31,95 @@ data class NetworkMessage(
|
||||
val payload: MessagePayload
|
||||
) : Streamable {
|
||||
|
||||
/**
|
||||
* First 4 bytes of sha512(payload)
|
||||
*/
|
||||
private fun getChecksum(bytes: ByteArray): ByteArray {
|
||||
val d = cryptography().sha512(bytes)
|
||||
return byteArrayOf(d[0], d[1], d[2], d[3])
|
||||
}
|
||||
override fun writer(): Writer = Writer(this)
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
// magic
|
||||
Encode.int32(MAGIC, out)
|
||||
class Writer internal constructor(
|
||||
private val item: NetworkMessage
|
||||
) : StreamableWriter {
|
||||
|
||||
// ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected)
|
||||
val command = payload.command.name.toLowerCase()
|
||||
out.write(command.toByteArray(charset("ASCII")))
|
||||
for (i in command.length..11) {
|
||||
out.write(0x0)
|
||||
override fun write(out: OutputStream) {
|
||||
// magic
|
||||
Encode.int32(MAGIC, out)
|
||||
|
||||
// ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected)
|
||||
val command = item.payload.command.name.toLowerCase()
|
||||
out.write(command.toByteArray(charset("ASCII")))
|
||||
for (i in command.length..11) {
|
||||
out.write(0x0)
|
||||
}
|
||||
|
||||
val payloadBytes = Encode.bytes(item.payload)
|
||||
|
||||
// Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would
|
||||
// ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are
|
||||
// larger than this.
|
||||
Encode.int32(payloadBytes.size, out)
|
||||
|
||||
// checksum
|
||||
out.write(getChecksum(payloadBytes))
|
||||
|
||||
// message payload
|
||||
out.write(payloadBytes)
|
||||
}
|
||||
|
||||
val payloadBytes = Encode.bytes(payload)
|
||||
/**
|
||||
* A more efficient implementation of the write method, writing header data to the provided buffer and returning
|
||||
* a new buffer containing the payload.
|
||||
|
||||
// Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would
|
||||
// ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are
|
||||
// larger than this.
|
||||
Encode.int32(payloadBytes.size, out)
|
||||
|
||||
// checksum
|
||||
out.write(getChecksum(payloadBytes))
|
||||
|
||||
// message payload
|
||||
out.write(payloadBytes)
|
||||
}
|
||||
|
||||
/**
|
||||
* A more efficient implementation of the write method, writing header data to the provided buffer and returning
|
||||
* a new buffer containing the payload.
|
||||
|
||||
* @param headerBuffer where the header data is written to (24 bytes)
|
||||
* *
|
||||
* @return a buffer containing the payload, ready to be read.
|
||||
*/
|
||||
fun writeHeaderAndGetPayloadBuffer(headerBuffer: ByteBuffer): ByteBuffer {
|
||||
return ByteBuffer.wrap(writeHeader(headerBuffer))
|
||||
}
|
||||
|
||||
/**
|
||||
* For improved memory efficiency, you should use [.writeHeaderAndGetPayloadBuffer]
|
||||
* and write the header buffer as well as the returned payload buffer into the channel.
|
||||
|
||||
* @param buffer where everything gets written to. Needs to be large enough for the whole message
|
||||
* * to be written.
|
||||
*/
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
val payloadBytes = writeHeader(buffer)
|
||||
buffer.put(payloadBytes)
|
||||
}
|
||||
|
||||
private fun writeHeader(out: ByteBuffer): ByteArray {
|
||||
// magic
|
||||
Encode.int32(MAGIC, out)
|
||||
|
||||
// ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected)
|
||||
val command = payload.command.name.toLowerCase()
|
||||
out.put(command.toByteArray(charset("ASCII")))
|
||||
|
||||
for (i in command.length..11) {
|
||||
out.put(0.toByte())
|
||||
* @param headerBuffer where the header data is written to (24 bytes)
|
||||
* *
|
||||
* @return a buffer containing the payload, ready to be read.
|
||||
*/
|
||||
fun writeHeaderAndGetPayloadBuffer(headerBuffer: ByteBuffer): ByteBuffer {
|
||||
return ByteBuffer.wrap(writeHeader(headerBuffer))
|
||||
}
|
||||
|
||||
val payloadBytes = Encode.bytes(payload)
|
||||
/**
|
||||
* For improved memory efficiency, you should use [.writeHeaderAndGetPayloadBuffer]
|
||||
* and write the header buffer as well as the returned payload buffer into the channel.
|
||||
|
||||
// Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would
|
||||
// ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are
|
||||
// larger than this.
|
||||
Encode.int32(payloadBytes.size, out)
|
||||
* @param buffer where everything gets written to. Needs to be large enough for the whole message
|
||||
* * to be written.
|
||||
*/
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
val payloadBytes = writeHeader(buffer)
|
||||
buffer.put(payloadBytes)
|
||||
}
|
||||
|
||||
// checksum
|
||||
out.put(getChecksum(payloadBytes))
|
||||
private fun writeHeader(out: ByteBuffer): ByteArray {
|
||||
// magic
|
||||
Encode.int32(MAGIC, out)
|
||||
|
||||
// ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected)
|
||||
val command = item.payload.command.name.toLowerCase()
|
||||
out.put(command.toByteArray(charset("ASCII")))
|
||||
|
||||
for (i in command.length..11) {
|
||||
out.put(0.toByte())
|
||||
}
|
||||
|
||||
val payloadBytes = Encode.bytes(item.payload)
|
||||
|
||||
// Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would
|
||||
// ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are
|
||||
// larger than this.
|
||||
Encode.int32(payloadBytes.size, out)
|
||||
|
||||
// checksum
|
||||
out.put(getChecksum(payloadBytes))
|
||||
|
||||
// message payload
|
||||
return payloadBytes
|
||||
}
|
||||
|
||||
/**
|
||||
* First 4 bytes of sha512(payload)
|
||||
*/
|
||||
private fun getChecksum(bytes: ByteArray): ByteArray {
|
||||
val d = cryptography().sha512(bytes)
|
||||
return byteArrayOf(d[0], d[1], d[2], d[3])
|
||||
}
|
||||
|
||||
// message payload
|
||||
return payloadBytes
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -39,36 +39,26 @@ data class ObjectMessage(
|
||||
var nonce: ByteArray? = null,
|
||||
val expiresTime: Long,
|
||||
val payload: ObjectPayload,
|
||||
val type: Long,
|
||||
val type: Long = payload.type?.number ?: throw IllegalArgumentException("payload must have type defined"),
|
||||
/**
|
||||
* The object's version
|
||||
*/
|
||||
val version: Long,
|
||||
val stream: Long
|
||||
val version: Long = payload.version,
|
||||
val stream: Long = payload.stream
|
||||
) : MessagePayload {
|
||||
|
||||
override val command: MessagePayload.Command = MessagePayload.Command.OBJECT
|
||||
|
||||
constructor(
|
||||
nonce: ByteArray? = null,
|
||||
expiresTime: Long,
|
||||
payload: ObjectPayload,
|
||||
stream: Long
|
||||
) : this(
|
||||
nonce,
|
||||
expiresTime,
|
||||
payload,
|
||||
payload.type?.number ?: throw IllegalArgumentException("payload must have type defined"),
|
||||
payload.version,
|
||||
stream
|
||||
)
|
||||
|
||||
val inventoryVector: InventoryVector
|
||||
get() {
|
||||
return InventoryVector(Bytes.truncate(cryptography().doubleSha512(
|
||||
nonce ?: throw IllegalStateException("nonce must be set"),
|
||||
payloadBytesWithoutNonce
|
||||
), 32))
|
||||
return InventoryVector(
|
||||
Bytes.truncate(
|
||||
cryptography().doubleSha512(
|
||||
nonce ?: throw IllegalStateException("nonce must be set"),
|
||||
payloadBytesWithoutNonce
|
||||
), 32
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private val isEncrypted: Boolean
|
||||
@ -81,8 +71,8 @@ data class ObjectMessage(
|
||||
get() {
|
||||
try {
|
||||
val out = ByteArrayOutputStream()
|
||||
writeHeaderWithoutNonce(out)
|
||||
payload.writeBytesToSign(out)
|
||||
writer.writeHeaderWithoutNonce(out)
|
||||
payload.writer().writeBytesToSign(out)
|
||||
return out.toByteArray()
|
||||
} catch (e: IOException) {
|
||||
throw ApplicationException(e)
|
||||
@ -131,30 +121,39 @@ data class ObjectMessage(
|
||||
return cryptography().isSignatureValid(bytesToSign, payload.signature ?: return false, pubkey)
|
||||
}
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
out.write(nonce ?: ByteArray(8))
|
||||
out.write(payloadBytesWithoutNonce)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
buffer.put(nonce ?: ByteArray(8))
|
||||
buffer.put(payloadBytesWithoutNonce)
|
||||
}
|
||||
|
||||
private fun writeHeaderWithoutNonce(out: OutputStream) {
|
||||
Encode.int64(expiresTime, out)
|
||||
Encode.int32(type, out)
|
||||
Encode.varInt(version, out)
|
||||
Encode.varInt(stream, out)
|
||||
}
|
||||
|
||||
val payloadBytesWithoutNonce: ByteArray by lazy {
|
||||
val out = ByteArrayOutputStream()
|
||||
writeHeaderWithoutNonce(out)
|
||||
payload.write(out)
|
||||
writer.writeHeaderWithoutNonce(out)
|
||||
payload.writer().write(out)
|
||||
out.toByteArray()
|
||||
}
|
||||
|
||||
private val writer = Writer(this)
|
||||
override fun writer(): StreamableWriter = writer
|
||||
|
||||
private class Writer(
|
||||
private val item: ObjectMessage
|
||||
) : StreamableWriter {
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
out.write(item.nonce ?: ByteArray(8))
|
||||
out.write(item.payloadBytesWithoutNonce)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
buffer.put(item.nonce ?: ByteArray(8))
|
||||
buffer.put(item.payloadBytesWithoutNonce)
|
||||
}
|
||||
|
||||
internal fun writeHeaderWithoutNonce(out: OutputStream) {
|
||||
Encode.int64(item.expiresTime, out)
|
||||
Encode.int32(item.type, out)
|
||||
Encode.varInt(item.version, out)
|
||||
Encode.varInt(item.stream, out)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Builder {
|
||||
private var nonce: ByteArray? = null
|
||||
private var expiresTime: Long = 0
|
||||
|
@ -35,6 +35,7 @@ import java.nio.ByteBuffer
|
||||
import java.util.*
|
||||
import java.util.Collections
|
||||
import kotlin.collections.HashSet
|
||||
import kotlin.collections.LinkedHashSet
|
||||
|
||||
private fun message(encoding: Plaintext.Encoding, subject: String, body: String): ByteArray = when (encoding) {
|
||||
SIMPLE -> "Subject:$subject\nBody:$body".toByteArray()
|
||||
@ -64,7 +65,7 @@ class Plaintext private constructor(
|
||||
val message: ByteArray,
|
||||
val ackData: ByteArray?,
|
||||
ackMessage: Lazy<ObjectMessage?> = lazy { Factory.createAck(from, ackData, ttl) },
|
||||
val conversationId: UUID = UUID.randomUUID(),
|
||||
var conversationId: UUID = UUID.randomUUID(),
|
||||
var inventoryVector: InventoryVector? = null,
|
||||
var signature: ByteArray? = null,
|
||||
sent: Long? = null,
|
||||
@ -86,10 +87,10 @@ class Plaintext private constructor(
|
||||
if (to == null) {
|
||||
return
|
||||
}
|
||||
if (this.to != null) {
|
||||
if (this.to!!.version != 0L)
|
||||
this.to?.let {
|
||||
if (it.version != 0L)
|
||||
throw IllegalStateException("Correct address already set")
|
||||
if (!Arrays.equals(this.to!!.ripe, to.ripe)) {
|
||||
if (!Arrays.equals(it.ripe, to.ripe)) {
|
||||
throw IllegalArgumentException("RIPEs don't match")
|
||||
}
|
||||
}
|
||||
@ -186,7 +187,8 @@ class Plaintext private constructor(
|
||||
Factory.getObjectMessage(
|
||||
3,
|
||||
ByteArrayInputStream(ackMessage),
|
||||
ackMessage.size)
|
||||
ackMessage.size
|
||||
)
|
||||
} else null
|
||||
},
|
||||
conversationId = conversationId,
|
||||
@ -242,7 +244,8 @@ class Plaintext private constructor(
|
||||
Factory.getObjectMessage(
|
||||
3,
|
||||
ByteArrayInputStream(ackMsg),
|
||||
ackMsg.size)
|
||||
ackMsg.size
|
||||
)
|
||||
} else {
|
||||
Factory.createAck(builder.from!!, builder.ackData, builder.ttl)
|
||||
}
|
||||
@ -254,108 +257,12 @@ class Plaintext private constructor(
|
||||
received = builder.received,
|
||||
initialHash = null,
|
||||
ttl = builder.ttl,
|
||||
labels = builder.labels,
|
||||
labels = LinkedHashSet(builder.labels),
|
||||
status = builder.status ?: Status.RECEIVED
|
||||
) {
|
||||
id = builder.id
|
||||
}
|
||||
|
||||
fun write(out: OutputStream, includeSignature: Boolean) {
|
||||
Encode.varInt(from.version, out)
|
||||
Encode.varInt(from.stream, out)
|
||||
from.pubkey?.apply {
|
||||
Encode.int32(behaviorBitfield, out)
|
||||
out.write(signingKey, 1, 64)
|
||||
out.write(encryptionKey, 1, 64)
|
||||
if (from.version >= 3) {
|
||||
Encode.varInt(nonceTrialsPerByte, out)
|
||||
Encode.varInt(extraBytes, out)
|
||||
}
|
||||
} ?: {
|
||||
Encode.int32(0, out)
|
||||
val empty = ByteArray(64)
|
||||
out.write(empty)
|
||||
out.write(empty)
|
||||
if (from.version >= 3) {
|
||||
Encode.varInt(0, out)
|
||||
Encode.varInt(0, out)
|
||||
}
|
||||
}.invoke()
|
||||
if (type == MSG) {
|
||||
out.write(to?.ripe ?: throw IllegalStateException("No recipient set for message"))
|
||||
}
|
||||
Encode.varInt(encodingCode, out)
|
||||
Encode.varInt(message.size, out)
|
||||
out.write(message)
|
||||
if (type == MSG) {
|
||||
if (to?.has(Feature.DOES_ACK) ?: false) {
|
||||
val ack = ByteArrayOutputStream()
|
||||
ackMessage?.write(ack)
|
||||
Encode.varBytes(ack.toByteArray(), out)
|
||||
} else {
|
||||
Encode.varInt(0, out)
|
||||
}
|
||||
}
|
||||
if (includeSignature) {
|
||||
if (signature == null) {
|
||||
Encode.varInt(0, out)
|
||||
} else {
|
||||
Encode.varBytes(signature!!, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun write(buffer: ByteBuffer, includeSignature: Boolean) {
|
||||
Encode.varInt(from.version, buffer)
|
||||
Encode.varInt(from.stream, buffer)
|
||||
if (from.pubkey == null) {
|
||||
Encode.int32(0, buffer)
|
||||
val empty = ByteArray(64)
|
||||
buffer.put(empty)
|
||||
buffer.put(empty)
|
||||
if (from.version >= 3) {
|
||||
Encode.varInt(0, buffer)
|
||||
Encode.varInt(0, buffer)
|
||||
}
|
||||
} else {
|
||||
Encode.int32(from.pubkey!!.behaviorBitfield, buffer)
|
||||
buffer.put(from.pubkey!!.signingKey, 1, 64)
|
||||
buffer.put(from.pubkey!!.encryptionKey, 1, 64)
|
||||
if (from.version >= 3) {
|
||||
Encode.varInt(from.pubkey!!.nonceTrialsPerByte, buffer)
|
||||
Encode.varInt(from.pubkey!!.extraBytes, buffer)
|
||||
}
|
||||
}
|
||||
if (type == MSG) {
|
||||
buffer.put(to!!.ripe)
|
||||
}
|
||||
Encode.varInt(encodingCode, buffer)
|
||||
Encode.varBytes(message, buffer)
|
||||
if (type == MSG) {
|
||||
if (to!!.has(Feature.DOES_ACK) && ackMessage != null) {
|
||||
Encode.varBytes(Encode.bytes(ackMessage!!), buffer)
|
||||
} else {
|
||||
Encode.varInt(0, buffer)
|
||||
}
|
||||
}
|
||||
if (includeSignature) {
|
||||
val sig = signature
|
||||
if (sig == null) {
|
||||
Encode.varInt(0, buffer)
|
||||
} else {
|
||||
Encode.varBytes(sig, buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
write(out, true)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
write(buffer, true)
|
||||
}
|
||||
|
||||
fun updateNextTry() {
|
||||
if (to != null) {
|
||||
if (nextTry == null) {
|
||||
@ -374,28 +281,30 @@ class Plaintext private constructor(
|
||||
get() {
|
||||
val s = Scanner(ByteArrayInputStream(message), "UTF-8")
|
||||
val firstLine = s.nextLine()
|
||||
if (encodingCode == EXTENDED.code) {
|
||||
if (Message.TYPE == extendedData?.type) {
|
||||
return (extendedData!!.content as? Message)?.subject
|
||||
return when (encodingCode) {
|
||||
EXTENDED.code -> if (Message.TYPE == extendedData?.type) {
|
||||
(extendedData!!.content as? Message)?.subject
|
||||
} else {
|
||||
return null
|
||||
null
|
||||
}
|
||||
SIMPLE.code -> firstLine.substring("Subject:".length).trim { it <= ' ' }
|
||||
else -> {
|
||||
if (firstLine.length > 50) {
|
||||
firstLine.substring(0, 50).trim { it <= ' ' } + "..."
|
||||
} else {
|
||||
firstLine
|
||||
}
|
||||
}
|
||||
} else if (encodingCode == SIMPLE.code) {
|
||||
return firstLine.substring("Subject:".length).trim { it <= ' ' }
|
||||
} else if (firstLine.length > 50) {
|
||||
return firstLine.substring(0, 50).trim { it <= ' ' } + "..."
|
||||
} else {
|
||||
return firstLine
|
||||
}
|
||||
}
|
||||
|
||||
val text: String?
|
||||
get() {
|
||||
if (encodingCode == EXTENDED.code) {
|
||||
if (Message.TYPE == extendedData?.type) {
|
||||
return (extendedData?.content as Message?)?.body
|
||||
return if (Message.TYPE == extendedData?.type) {
|
||||
(extendedData?.content as Message?)?.body
|
||||
} else {
|
||||
return null
|
||||
null
|
||||
}
|
||||
} else {
|
||||
val text = String(message)
|
||||
@ -418,20 +327,20 @@ class Plaintext private constructor(
|
||||
val parents: List<InventoryVector>
|
||||
get() {
|
||||
val extendedData = extendedData ?: return emptyList()
|
||||
if (Message.TYPE == extendedData.type) {
|
||||
return (extendedData.content as Message).parents
|
||||
return if (Message.TYPE == extendedData.type) {
|
||||
(extendedData.content as Message).parents
|
||||
} else {
|
||||
return emptyList()
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
val files: List<Attachment>
|
||||
get() {
|
||||
val extendedData = extendedData ?: return emptyList()
|
||||
if (Message.TYPE == extendedData.type) {
|
||||
return (extendedData.content as Message).files
|
||||
return if (Message.TYPE == extendedData.type) {
|
||||
(extendedData.content as Message).files
|
||||
} else {
|
||||
return emptyList()
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
@ -474,8 +383,8 @@ class Plaintext private constructor(
|
||||
|
||||
override fun toString(): String {
|
||||
val subject = subject
|
||||
if (subject?.isNotEmpty() ?: false) {
|
||||
return subject!!
|
||||
if (subject?.isNotEmpty() == true) {
|
||||
return subject
|
||||
} else {
|
||||
return Strings.hex(
|
||||
initialHash ?: return super.toString()
|
||||
@ -484,6 +393,7 @@ class Plaintext private constructor(
|
||||
}
|
||||
|
||||
enum class Encoding constructor(code: Long) {
|
||||
|
||||
IGNORE(0), TRIVIAL(1), SIMPLE(2), EXTENDED(3);
|
||||
|
||||
var code: Long = 0
|
||||
@ -495,7 +405,8 @@ class Plaintext private constructor(
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic fun fromCode(code: Long): Encoding? {
|
||||
@JvmStatic
|
||||
fun fromCode(code: Long): Encoding? {
|
||||
for (e in values()) {
|
||||
if (e.code == code) {
|
||||
return e
|
||||
@ -503,12 +414,13 @@ class Plaintext private constructor(
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
enum class Status {
|
||||
|
||||
DRAFT,
|
||||
// For sent messages
|
||||
PUBKEY_REQUESTED,
|
||||
DOING_PROOF_OF_WORK,
|
||||
SENT,
|
||||
@ -517,36 +429,158 @@ class Plaintext private constructor(
|
||||
}
|
||||
|
||||
enum class Type {
|
||||
|
||||
MSG, BROADCAST
|
||||
}
|
||||
|
||||
fun writer(includeSignature: Boolean): StreamableWriter = Writer(this, includeSignature)
|
||||
|
||||
override fun writer(): StreamableWriter = Writer(this)
|
||||
|
||||
private class Writer(
|
||||
private val item: Plaintext,
|
||||
private val includeSignature: Boolean = true
|
||||
) : StreamableWriter {
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
Encode.varInt(item.from.version, out)
|
||||
Encode.varInt(item.from.stream, out)
|
||||
item.from.pubkey?.apply {
|
||||
Encode.int32(behaviorBitfield, out)
|
||||
out.write(signingKey, 1, 64)
|
||||
out.write(encryptionKey, 1, 64)
|
||||
if (item.from.version >= 3) {
|
||||
Encode.varInt(nonceTrialsPerByte, out)
|
||||
Encode.varInt(extraBytes, out)
|
||||
}
|
||||
} ?: {
|
||||
Encode.int32(0, out)
|
||||
val empty = ByteArray(64)
|
||||
out.write(empty)
|
||||
out.write(empty)
|
||||
if (item.from.version >= 3) {
|
||||
Encode.varInt(0, out)
|
||||
Encode.varInt(0, out)
|
||||
}
|
||||
}.invoke()
|
||||
if (item.type == MSG) {
|
||||
// A draft without recipient is allowed, therefore this workaround.
|
||||
item.to?.let { out.write(it.ripe) } ?: if (item.status == Status.DRAFT) {
|
||||
out.write(ByteArray(20))
|
||||
} else {
|
||||
throw IllegalStateException("No recipient set for message")
|
||||
}
|
||||
}
|
||||
Encode.varInt(item.encodingCode, out)
|
||||
Encode.varInt(item.message.size, out)
|
||||
out.write(item.message)
|
||||
if (item.type == MSG) {
|
||||
if (item.to?.has(Feature.DOES_ACK) == true) {
|
||||
val ack = ByteArrayOutputStream()
|
||||
item.ackMessage?.writer()?.write(ack)
|
||||
Encode.varBytes(ack.toByteArray(), out)
|
||||
} else {
|
||||
Encode.varInt(0, out)
|
||||
}
|
||||
}
|
||||
if (includeSignature) {
|
||||
val sig = item.signature
|
||||
if (sig == null) {
|
||||
Encode.varInt(0, out)
|
||||
} else {
|
||||
Encode.varBytes(sig, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
Encode.varInt(item.from.version, buffer)
|
||||
Encode.varInt(item.from.stream, buffer)
|
||||
if (item.from.pubkey == null) {
|
||||
Encode.int32(0, buffer)
|
||||
val empty = ByteArray(64)
|
||||
buffer.put(empty)
|
||||
buffer.put(empty)
|
||||
if (item.from.version >= 3) {
|
||||
Encode.varInt(0, buffer)
|
||||
Encode.varInt(0, buffer)
|
||||
}
|
||||
} else {
|
||||
Encode.int32(item.from.pubkey!!.behaviorBitfield, buffer)
|
||||
buffer.put(item.from.pubkey!!.signingKey, 1, 64)
|
||||
buffer.put(item.from.pubkey!!.encryptionKey, 1, 64)
|
||||
if (item.from.version >= 3) {
|
||||
Encode.varInt(item.from.pubkey!!.nonceTrialsPerByte, buffer)
|
||||
Encode.varInt(item.from.pubkey!!.extraBytes, buffer)
|
||||
}
|
||||
}
|
||||
if (item.type == MSG) {
|
||||
// A draft without recipient is allowed, therefore this workaround.
|
||||
item.to?.let { buffer.put(it.ripe) } ?: if (item.status == Status.DRAFT) {
|
||||
buffer.put(ByteArray(20))
|
||||
} else {
|
||||
throw IllegalStateException("No recipient set for message")
|
||||
}
|
||||
}
|
||||
Encode.varInt(item.encodingCode, buffer)
|
||||
Encode.varBytes(item.message, buffer)
|
||||
if (item.type == MSG) {
|
||||
if (item.to!!.has(Feature.DOES_ACK) && item.ackMessage != null) {
|
||||
Encode.varBytes(Encode.bytes(item.ackMessage!!), buffer)
|
||||
} else {
|
||||
Encode.varInt(0, buffer)
|
||||
}
|
||||
}
|
||||
if (includeSignature) {
|
||||
val sig = item.signature
|
||||
if (sig == null) {
|
||||
Encode.varInt(0, buffer)
|
||||
} else {
|
||||
Encode.varBytes(sig, buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Builder(internal val type: Type) {
|
||||
internal var id: Any? = null
|
||||
internal var inventoryVector: InventoryVector? = null
|
||||
internal var from: BitmessageAddress? = null
|
||||
internal var to: BitmessageAddress? = null
|
||||
private var addressVersion: Long = 0
|
||||
private var stream: Long = 0
|
||||
private var behaviorBitfield: Int = 0
|
||||
private var publicSigningKey: ByteArray? = null
|
||||
private var publicEncryptionKey: ByteArray? = null
|
||||
private var nonceTrialsPerByte: Long = 0
|
||||
private var extraBytes: Long = 0
|
||||
private var destinationRipe: ByteArray? = null
|
||||
private var preventAck: Boolean = false
|
||||
internal var encoding: Long = 0
|
||||
internal var message = ByteArray(0)
|
||||
internal var ackData: ByteArray? = null
|
||||
internal var ackMessage: ByteArray? = null
|
||||
internal var signature: ByteArray? = null
|
||||
internal var sent: Long? = null
|
||||
internal var received: Long? = null
|
||||
internal var status: Status? = null
|
||||
internal val labels = LinkedHashSet<Label>()
|
||||
internal var ttl: Long = 0
|
||||
internal var retries: Int = 0
|
||||
internal var nextTry: Long? = null
|
||||
internal var conversation: UUID? = null
|
||||
var id: Any? = null
|
||||
var inventoryVector: InventoryVector? = null
|
||||
var from: BitmessageAddress? = null
|
||||
var to: BitmessageAddress? = null
|
||||
set(value) {
|
||||
if (value != null) {
|
||||
if (type != MSG && to != null)
|
||||
throw IllegalArgumentException("recipient address only allowed for msg")
|
||||
field = value
|
||||
}
|
||||
}
|
||||
var addressVersion: Long = 0
|
||||
var stream: Long = 0
|
||||
var behaviorBitfield: Int = 0
|
||||
var publicSigningKey: ByteArray? = null
|
||||
var publicEncryptionKey: ByteArray? = null
|
||||
var nonceTrialsPerByte: Long = 0
|
||||
var extraBytes: Long = 0
|
||||
var destinationRipe: ByteArray? = null
|
||||
set(value) {
|
||||
if (type != MSG && value != null) throw IllegalArgumentException("ripe only allowed for msg")
|
||||
field = value
|
||||
}
|
||||
var preventAck: Boolean = false
|
||||
var encoding: Long = 0
|
||||
var message = ByteArray(0)
|
||||
var ackData: ByteArray? = null
|
||||
var ackMessage: ByteArray? = null
|
||||
var signature: ByteArray? = null
|
||||
var sent: Long? = null
|
||||
var received: Long? = null
|
||||
var status: Status? = null
|
||||
var labels: Collection<Label> = emptySet()
|
||||
var ttl: Long = 0
|
||||
var retries: Int = 0
|
||||
var nextTry: Long? = null
|
||||
var conversation: UUID? = null
|
||||
|
||||
fun id(id: Any): Builder {
|
||||
this.id = id
|
||||
@ -564,11 +598,7 @@ class Plaintext private constructor(
|
||||
}
|
||||
|
||||
fun to(address: BitmessageAddress?): Builder {
|
||||
if (address != null) {
|
||||
if (type != MSG && to != null)
|
||||
throw IllegalArgumentException("recipient address only allowed for msg")
|
||||
to = address
|
||||
}
|
||||
to = address
|
||||
return this
|
||||
}
|
||||
|
||||
@ -608,7 +638,6 @@ class Plaintext private constructor(
|
||||
}
|
||||
|
||||
fun destinationRipe(ripe: ByteArray?): Builder {
|
||||
if (type != MSG && ripe != null) throw IllegalArgumentException("ripe only allowed for msg")
|
||||
this.destinationRipe = ripe
|
||||
return this
|
||||
}
|
||||
@ -684,7 +713,7 @@ class Plaintext private constructor(
|
||||
}
|
||||
|
||||
fun labels(labels: Collection<Label>): Builder {
|
||||
this.labels.addAll(labels)
|
||||
this.labels = labels
|
||||
return this
|
||||
}
|
||||
|
||||
@ -710,15 +739,17 @@ class Plaintext private constructor(
|
||||
|
||||
internal fun prepare(): Builder {
|
||||
if (from == null) {
|
||||
from = BitmessageAddress(Factory.createPubkey(
|
||||
addressVersion,
|
||||
stream,
|
||||
publicSigningKey!!,
|
||||
publicEncryptionKey!!,
|
||||
nonceTrialsPerByte,
|
||||
extraBytes,
|
||||
behaviorBitfield
|
||||
))
|
||||
from = BitmessageAddress(
|
||||
Factory.createPubkey(
|
||||
addressVersion,
|
||||
stream,
|
||||
publicSigningKey!!,
|
||||
publicEncryptionKey!!,
|
||||
nonceTrialsPerByte,
|
||||
extraBytes,
|
||||
behaviorBitfield
|
||||
)
|
||||
)
|
||||
}
|
||||
if (to == null && type != Type.BROADCAST && destinationRipe != null) {
|
||||
to = BitmessageAddress(0, 0, destinationRipe!!)
|
||||
@ -726,7 +757,7 @@ class Plaintext private constructor(
|
||||
if (preventAck) {
|
||||
ackData = null
|
||||
ackMessage = null
|
||||
} else if (type == MSG && ackMessage == null && ackData == null) {
|
||||
} else if (type == MSG && ackMessage == null && ackData == null && to?.has(Feature.DOES_ACK) == true) {
|
||||
ackData = cryptography().randomBytes(Msg.ACK_LENGTH)
|
||||
}
|
||||
if (ttl <= 0) {
|
||||
@ -735,6 +766,12 @@ class Plaintext private constructor(
|
||||
return this
|
||||
}
|
||||
|
||||
@JvmSynthetic
|
||||
inline fun build(block: Builder.() -> Unit): Plaintext {
|
||||
block(this)
|
||||
return build()
|
||||
}
|
||||
|
||||
fun build(): Plaintext {
|
||||
return Plaintext(this)
|
||||
}
|
||||
@ -742,27 +779,54 @@ class Plaintext private constructor(
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic fun read(type: Type, `in`: InputStream): Plaintext {
|
||||
return readWithoutSignature(type, `in`)
|
||||
.signature(Decode.varBytes(`in`))
|
||||
@JvmStatic
|
||||
fun read(type: Type, input: InputStream): Plaintext {
|
||||
return readWithoutSignature(type, input)
|
||||
.signature(Decode.varBytes(input))
|
||||
.received(UnixTime.now)
|
||||
.build()
|
||||
}
|
||||
|
||||
@JvmStatic fun readWithoutSignature(type: Type, `in`: InputStream): Plaintext.Builder {
|
||||
val version = Decode.varInt(`in`)
|
||||
@JvmStatic
|
||||
fun readWithoutSignature(type: Type, input: InputStream): Plaintext.Builder {
|
||||
val version = Decode.varInt(input)
|
||||
return Builder(type)
|
||||
.addressVersion(version)
|
||||
.stream(Decode.varInt(`in`))
|
||||
.behaviorBitfield(Decode.int32(`in`))
|
||||
.publicSigningKey(Decode.bytes(`in`, 64))
|
||||
.publicEncryptionKey(Decode.bytes(`in`, 64))
|
||||
.nonceTrialsPerByte(if (version >= 3) Decode.varInt(`in`) else 0)
|
||||
.extraBytes(if (version >= 3) Decode.varInt(`in`) else 0)
|
||||
.destinationRipe(if (type == MSG) Decode.bytes(`in`, 20) else null)
|
||||
.encoding(Decode.varInt(`in`))
|
||||
.message(Decode.varBytes(`in`))
|
||||
.ackMessage(if (type == MSG) Decode.varBytes(`in`) else null)
|
||||
.stream(Decode.varInt(input))
|
||||
.behaviorBitfield(Decode.int32(input))
|
||||
.publicSigningKey(Decode.bytes(input, 64))
|
||||
.publicEncryptionKey(Decode.bytes(input, 64))
|
||||
.nonceTrialsPerByte(if (version >= 3) Decode.varInt(input) else 0)
|
||||
.extraBytes(if (version >= 3) Decode.varInt(input) else 0)
|
||||
.destinationRipe(if (type == MSG) Decode.bytes(input, 20).let {
|
||||
if (it.any { x -> x != 0.toByte() }) it else null
|
||||
} else null)
|
||||
.encoding(Decode.varInt(input))
|
||||
.message(Decode.varBytes(input))
|
||||
.ackMessage(if (type == MSG) Decode.varBytes(input) else null)
|
||||
}
|
||||
|
||||
@JvmSynthetic
|
||||
inline fun build(type: Type, block: Builder.() -> Unit): Plaintext {
|
||||
val builder = Builder(type)
|
||||
block(builder)
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
data class Conversation(val id: UUID, val subject: String, val messages: List<Plaintext>) : Serializable {
|
||||
val participants = messages
|
||||
.map { it.from }
|
||||
.filter { it.privateKey == null || it.isChan }
|
||||
.distinct()
|
||||
|
||||
val extract: String by lazy {
|
||||
messages.firstOrNull { m -> m.labels.any { l -> l.type==Label.Type.UNREAD } }?.text
|
||||
?: messages.lastOrNull()?.text
|
||||
?: ""
|
||||
}
|
||||
|
||||
fun hasUnread() = messages.any { it.isUnread() }
|
||||
}
|
||||
|
@ -24,7 +24,27 @@ import java.nio.ByteBuffer
|
||||
* An object that can be written to an [OutputStream]
|
||||
*/
|
||||
interface Streamable : Serializable {
|
||||
fun write(out: OutputStream)
|
||||
fun writer(): StreamableWriter
|
||||
}
|
||||
|
||||
interface SignedStreamable : Streamable {
|
||||
override fun writer(): SignedStreamableWriter
|
||||
}
|
||||
|
||||
interface EncryptedStreamable : SignedStreamable {
|
||||
override fun writer(): EncryptedStreamableWriter
|
||||
}
|
||||
|
||||
interface StreamableWriter: Serializable {
|
||||
fun write(out: OutputStream)
|
||||
fun write(buffer: ByteBuffer)
|
||||
}
|
||||
|
||||
interface SignedStreamableWriter : StreamableWriter {
|
||||
fun writeBytesToSign(out: OutputStream)
|
||||
}
|
||||
|
||||
interface EncryptedStreamableWriter : SignedStreamableWriter {
|
||||
fun writeUnencrypted(out: OutputStream)
|
||||
fun writeUnencrypted(buffer: ByteBuffer)
|
||||
}
|
||||
|
@ -26,12 +26,12 @@ class VerAck : MessagePayload {
|
||||
|
||||
override val command: MessagePayload.Command = MessagePayload.Command.VERACK
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
// 'verack' doesn't have any payload, so there is nothing to write
|
||||
}
|
||||
// 'verack' doesn't have any payload, so there is nothing to write
|
||||
override fun writer(): StreamableWriter = EmptyWriter
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
// 'verack' doesn't have any payload, so there is nothing to write
|
||||
internal object EmptyWriter : StreamableWriter {
|
||||
override fun write(out: OutputStream) = Unit
|
||||
override fun write(buffer: ByteBuffer) = Unit
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -71,32 +71,36 @@ class Version constructor(
|
||||
val streams: LongArray = longArrayOf(1)
|
||||
) : MessagePayload {
|
||||
|
||||
fun provides(service: Service?): Boolean {
|
||||
return service != null && service.isEnabled(services)
|
||||
}
|
||||
fun provides(service: Service?) = service?.isEnabled(services) == true
|
||||
|
||||
override val command: MessagePayload.Command = MessagePayload.Command.VERSION
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
Encode.int32(version, out)
|
||||
Encode.int64(services, out)
|
||||
Encode.int64(timestamp, out)
|
||||
addrRecv.write(out, true)
|
||||
addrFrom.write(out, true)
|
||||
Encode.int64(nonce, out)
|
||||
Encode.varString(userAgent, out)
|
||||
Encode.varIntList(streams, out)
|
||||
}
|
||||
override fun writer(): StreamableWriter = Writer(this)
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
Encode.int32(version, buffer)
|
||||
Encode.int64(services, buffer)
|
||||
Encode.int64(timestamp, buffer)
|
||||
addrRecv.write(buffer, true)
|
||||
addrFrom.write(buffer, true)
|
||||
Encode.int64(nonce, buffer)
|
||||
Encode.varString(userAgent, buffer)
|
||||
Encode.varIntList(streams, buffer)
|
||||
private class Writer(
|
||||
private val item: Version
|
||||
) : StreamableWriter {
|
||||
override fun write(out: OutputStream) {
|
||||
Encode.int32(item.version, out)
|
||||
Encode.int64(item.services, out)
|
||||
Encode.int64(item.timestamp, out)
|
||||
item.addrRecv.writer(true).write(out)
|
||||
item.addrFrom.writer(true).write(out)
|
||||
Encode.int64(item.nonce, out)
|
||||
Encode.varString(item.userAgent, out)
|
||||
Encode.varIntList(item.streams, out)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
Encode.int32(item.version, buffer)
|
||||
Encode.int64(item.services, buffer)
|
||||
Encode.int64(item.timestamp, buffer)
|
||||
item.addrRecv.writer(true).write(buffer)
|
||||
item.addrFrom.writer(true).write(buffer)
|
||||
Encode.int64(item.nonce, buffer)
|
||||
Encode.varString(item.userAgent, buffer)
|
||||
Encode.varIntList(item.streams, buffer)
|
||||
}
|
||||
}
|
||||
|
||||
class Builder {
|
||||
@ -187,9 +191,7 @@ class Version constructor(
|
||||
// TODO: NODE_SSL(2);
|
||||
NODE_NETWORK(1);
|
||||
|
||||
fun isEnabled(flag: Long): Boolean {
|
||||
return (flag and this.flag) != 0L
|
||||
}
|
||||
fun isEnabled(flag: Long) = (flag and this.flag) != 0L
|
||||
|
||||
companion object {
|
||||
fun getServiceFlag(vararg services: Service): Long {
|
||||
|
@ -29,7 +29,12 @@ import java.util.*
|
||||
* Users who are subscribed to the sending address will see the message appear in their inbox.
|
||||
* Broadcasts are version 4 or 5.
|
||||
*/
|
||||
abstract class Broadcast protected constructor(version: Long, override val stream: Long, protected var encrypted: CryptoBox?, override var plaintext: Plaintext?) : ObjectPayload(version), Encrypted, PlaintextHolder {
|
||||
abstract class Broadcast protected constructor(
|
||||
version: Long,
|
||||
override val stream: Long,
|
||||
protected var encrypted: CryptoBox?,
|
||||
override var plaintext: Plaintext?
|
||||
) : ObjectPayload(version), Encrypted, PlaintextHolder {
|
||||
|
||||
override val isSigned: Boolean = true
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
package ch.dissem.bitmessage.entity.payload
|
||||
|
||||
import ch.dissem.bitmessage.entity.Streamable
|
||||
import ch.dissem.bitmessage.entity.StreamableWriter
|
||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey.Companion.PRIVATE_KEY_SIZE
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
||||
import ch.dissem.bitmessage.utils.*
|
||||
@ -108,44 +109,53 @@ class CryptoBox : Streamable {
|
||||
|
||||
private fun calculateMac(key_m: ByteArray): ByteArray {
|
||||
val macData = ByteArrayOutputStream()
|
||||
writeWithoutMAC(macData)
|
||||
writer.writeWithoutMAC(macData)
|
||||
return cryptography().mac(key_m, macData.toByteArray())
|
||||
}
|
||||
|
||||
private fun writeWithoutMAC(out: OutputStream) {
|
||||
out.write(initializationVector)
|
||||
Encode.int16(curveType, out)
|
||||
writeCoordinateComponent(out, Points.getX(R))
|
||||
writeCoordinateComponent(out, Points.getY(R))
|
||||
out.write(encrypted)
|
||||
}
|
||||
private val writer = Writer(this)
|
||||
override fun writer(): StreamableWriter = writer
|
||||
|
||||
private fun writeCoordinateComponent(out: OutputStream, x: ByteArray) {
|
||||
val offset = Bytes.numberOfLeadingZeros(x)
|
||||
val length = x.size - offset
|
||||
Encode.int16(length, out)
|
||||
out.write(x, offset, length)
|
||||
}
|
||||
private class Writer(
|
||||
private val item: CryptoBox
|
||||
) : StreamableWriter {
|
||||
|
||||
private fun writeCoordinateComponent(buffer: ByteBuffer, x: ByteArray) {
|
||||
val offset = Bytes.numberOfLeadingZeros(x)
|
||||
val length = x.size - offset
|
||||
Encode.int16(length, buffer)
|
||||
buffer.put(x, offset, length)
|
||||
}
|
||||
override fun write(out: OutputStream) {
|
||||
writeWithoutMAC(out)
|
||||
out.write(item.mac)
|
||||
}
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
writeWithoutMAC(out)
|
||||
out.write(mac)
|
||||
}
|
||||
internal fun writeWithoutMAC(out: OutputStream) {
|
||||
out.write(item.initializationVector)
|
||||
Encode.int16(item.curveType, out)
|
||||
writeCoordinateComponent(out, Points.getX(item.R))
|
||||
writeCoordinateComponent(out, Points.getY(item.R))
|
||||
out.write(item.encrypted)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
buffer.put(item.initializationVector)
|
||||
Encode.int16(item.curveType, buffer)
|
||||
writeCoordinateComponent(buffer, Points.getX(item.R))
|
||||
writeCoordinateComponent(buffer, Points.getY(item.R))
|
||||
buffer.put(item.encrypted)
|
||||
buffer.put(item.mac)
|
||||
}
|
||||
|
||||
private fun writeCoordinateComponent(out: OutputStream, x: ByteArray) {
|
||||
val offset = Bytes.numberOfLeadingZeros(x)
|
||||
val length = x.size - offset
|
||||
Encode.int16(length, out)
|
||||
out.write(x, offset, length)
|
||||
}
|
||||
|
||||
private fun writeCoordinateComponent(buffer: ByteBuffer, x: ByteArray) {
|
||||
val offset = Bytes.numberOfLeadingZeros(x)
|
||||
val length = x.size - offset
|
||||
Encode.int16(length, buffer)
|
||||
buffer.put(x, offset, length)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
buffer.put(initializationVector)
|
||||
Encode.int16(curveType, buffer)
|
||||
writeCoordinateComponent(buffer, Points.getX(R))
|
||||
writeCoordinateComponent(buffer, Points.getY(R))
|
||||
buffer.put(encrypted)
|
||||
buffer.put(mac)
|
||||
}
|
||||
|
||||
class Builder {
|
||||
@ -187,15 +197,14 @@ class CryptoBox : Streamable {
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(): CryptoBox {
|
||||
return CryptoBox(this)
|
||||
}
|
||||
fun build() = CryptoBox(this)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOG = LoggerFactory.getLogger(CryptoBox::class.java)
|
||||
|
||||
@JvmStatic fun read(stream: InputStream, length: Int): CryptoBox {
|
||||
@JvmStatic
|
||||
fun read(stream: InputStream, length: Int): CryptoBox {
|
||||
val counter = AccessCounter()
|
||||
return Builder()
|
||||
.IV(Decode.bytes(stream, 16, counter))
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
package ch.dissem.bitmessage.entity.payload
|
||||
|
||||
import ch.dissem.bitmessage.entity.SignedStreamableWriter
|
||||
import ch.dissem.bitmessage.utils.Decode
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
@ -30,14 +31,6 @@ class GenericPayload(version: Long, override val stream: Long, val data: ByteArr
|
||||
|
||||
override val type: ObjectType? = null
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
out.write(data)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
buffer.put(data)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is GenericPayload) return false
|
||||
@ -52,9 +45,27 @@ class GenericPayload(version: Long, override val stream: Long, val data: ByteArr
|
||||
return result
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic fun read(version: Long, stream: Long, `is`: InputStream, length: Int): GenericPayload {
|
||||
return GenericPayload(version, stream, Decode.bytes(`is`, length))
|
||||
override fun writer(): SignedStreamableWriter = Writer(this)
|
||||
|
||||
private class Writer(
|
||||
private val item: GenericPayload
|
||||
) : SignedStreamableWriter {
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
out.write(item.data)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
buffer.put(item.data)
|
||||
}
|
||||
|
||||
override fun writeBytesToSign(out: OutputStream) = Unit // nothing to do
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun read(version: Long, stream: Long, input: InputStream, length: Int) =
|
||||
GenericPayload(version, stream, Decode.bytes(input, length))
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
package ch.dissem.bitmessage.entity.payload
|
||||
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||
import ch.dissem.bitmessage.entity.SignedStreamableWriter
|
||||
import ch.dissem.bitmessage.utils.Decode
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
@ -47,17 +48,28 @@ class GetPubkey : ObjectPayload {
|
||||
this.ripeTag = ripeOrTag
|
||||
}
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
out.write(ripeTag)
|
||||
}
|
||||
override fun writer(): SignedStreamableWriter = Writer(this)
|
||||
|
||||
private class Writer(
|
||||
private val item: GetPubkey
|
||||
) : SignedStreamableWriter {
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
out.write(item.ripeTag)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
buffer.put(item.ripeTag)
|
||||
}
|
||||
|
||||
override fun writeBytesToSign(out: OutputStream) = Unit // nothing to sign
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
buffer.put(ripeTag)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic fun read(`is`: InputStream, stream: Long, length: Int, version: Long): GetPubkey {
|
||||
return GetPubkey(version, stream, Decode.bytes(`is`, length))
|
||||
@JvmStatic
|
||||
fun read(input: InputStream, stream: Long, length: Int, version: Long): GetPubkey {
|
||||
return GetPubkey(version, stream, Decode.bytes(input, length))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import ch.dissem.bitmessage.entity.Encrypted
|
||||
import ch.dissem.bitmessage.entity.Plaintext
|
||||
import ch.dissem.bitmessage.entity.Plaintext.Type.MSG
|
||||
import ch.dissem.bitmessage.entity.PlaintextHolder
|
||||
import ch.dissem.bitmessage.entity.SignedStreamableWriter
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
@ -51,10 +52,6 @@ class Msg : ObjectPayload, Encrypted, PlaintextHolder {
|
||||
|
||||
override val isSigned: Boolean = true
|
||||
|
||||
override fun writeBytesToSign(out: OutputStream) {
|
||||
plaintext?.write(out, false) ?: throw IllegalStateException("no plaintext data available")
|
||||
}
|
||||
|
||||
override var signature: ByteArray?
|
||||
get() = plaintext?.signature
|
||||
set(signature) {
|
||||
@ -73,14 +70,6 @@ class Msg : ObjectPayload, Encrypted, PlaintextHolder {
|
||||
override val isDecrypted: Boolean
|
||||
get() = plaintext != null
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
encrypted?.write(out) ?: throw IllegalStateException("Msg must be signed and encrypted before writing it.")
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
encrypted?.write(buffer) ?: throw IllegalStateException("Msg must be signed and encrypted before writing it.")
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is Msg) return false
|
||||
@ -89,15 +78,34 @@ class Msg : ObjectPayload, Encrypted, PlaintextHolder {
|
||||
return stream == other.stream && (encrypted == other.encrypted || plaintext == other.plaintext)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return stream.toInt()
|
||||
override fun hashCode() = stream.toInt()
|
||||
|
||||
override fun writer(): SignedStreamableWriter = Writer(this)
|
||||
|
||||
private class Writer(
|
||||
private val item: Msg
|
||||
) : SignedStreamableWriter {
|
||||
|
||||
val encryptedDataWriter = item.encrypted?.writer()
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
encryptedDataWriter?.write(out) ?: throw IllegalStateException("Msg must be signed and encrypted before writing it.")
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
encryptedDataWriter?.write(buffer) ?: throw IllegalStateException("Msg must be signed and encrypted before writing it.")
|
||||
}
|
||||
|
||||
override fun writeBytesToSign(out: OutputStream) {
|
||||
item.plaintext?.writer(false)?.write(out) ?: throw IllegalStateException("no plaintext data available")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ACK_LENGTH = 32
|
||||
|
||||
@JvmStatic fun read(`in`: InputStream, stream: Long, length: Int): Msg {
|
||||
return Msg(stream, CryptoBox.read(`in`, length))
|
||||
}
|
||||
@JvmStatic
|
||||
fun read(input: InputStream, stream: Long, length: Int) = Msg(stream, CryptoBox.read(input, length))
|
||||
}
|
||||
}
|
||||
|
@ -17,13 +17,14 @@
|
||||
package ch.dissem.bitmessage.entity.payload
|
||||
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage
|
||||
import ch.dissem.bitmessage.entity.SignedStreamable
|
||||
import ch.dissem.bitmessage.entity.Streamable
|
||||
import java.io.OutputStream
|
||||
|
||||
/**
|
||||
* The payload of an 'object' command. This is shared by the network.
|
||||
*/
|
||||
abstract class ObjectPayload protected constructor(val version: Long) : Streamable {
|
||||
abstract class ObjectPayload protected constructor(val version: Long) : SignedStreamable {
|
||||
|
||||
abstract val type: ObjectType?
|
||||
|
||||
@ -31,10 +32,6 @@ abstract class ObjectPayload protected constructor(val version: Long) : Streamab
|
||||
|
||||
open val isSigned: Boolean = false
|
||||
|
||||
open fun writeBytesToSign(out: OutputStream) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the ECDSA signature which, as of protocol v3, covers the object header starting with the time,
|
||||
* * appended with the data described in this table down to the extra_bytes. Therefore, this must
|
||||
|
@ -18,6 +18,8 @@ package ch.dissem.bitmessage.entity.payload
|
||||
|
||||
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES
|
||||
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE
|
||||
import ch.dissem.bitmessage.entity.EncryptedStreamableWriter
|
||||
import ch.dissem.bitmessage.entity.SignedStreamableWriter
|
||||
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
@ -42,13 +44,7 @@ abstract class Pubkey protected constructor(version: Long) : ObjectPayload(versi
|
||||
|
||||
open val extraBytes: Long = NETWORK_EXTRA_BYTES
|
||||
|
||||
open fun writeUnencrypted(out: OutputStream) {
|
||||
write(out)
|
||||
}
|
||||
|
||||
open fun writeUnencrypted(buffer: ByteBuffer) {
|
||||
write(buffer)
|
||||
}
|
||||
abstract override fun writer(): EncryptedStreamableWriter
|
||||
|
||||
/**
|
||||
* Bits 0 through 29 are yet undefined
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
package ch.dissem.bitmessage.entity.payload
|
||||
|
||||
import ch.dissem.bitmessage.entity.EncryptedStreamableWriter
|
||||
import ch.dissem.bitmessage.utils.Decode
|
||||
import ch.dissem.bitmessage.utils.Encode
|
||||
import java.io.InputStream
|
||||
@ -25,21 +26,47 @@ import java.nio.ByteBuffer
|
||||
/**
|
||||
* A version 2 public key.
|
||||
*/
|
||||
open class V2Pubkey constructor(version: Long, override val stream: Long, override val behaviorBitfield: Int, signingKey: ByteArray, encryptionKey: ByteArray) : Pubkey(version) {
|
||||
open class V2Pubkey constructor(
|
||||
version: Long,
|
||||
override val stream: Long,
|
||||
override val behaviorBitfield: Int,
|
||||
signingKey: ByteArray,
|
||||
encryptionKey: ByteArray
|
||||
) : Pubkey(version) {
|
||||
|
||||
override val signingKey: ByteArray = if (signingKey.size == 64) add0x04(signingKey) else signingKey
|
||||
override val encryptionKey: ByteArray = if (encryptionKey.size == 64) add0x04(encryptionKey) else encryptionKey
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
Encode.int32(behaviorBitfield, out)
|
||||
out.write(signingKey, 1, 64)
|
||||
out.write(encryptionKey, 1, 64)
|
||||
}
|
||||
override fun writer(): EncryptedStreamableWriter = Writer(this)
|
||||
|
||||
protected open class Writer(
|
||||
private val item: V2Pubkey
|
||||
) : EncryptedStreamableWriter {
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
Encode.int32(item.behaviorBitfield, out)
|
||||
out.write(item.signingKey, 1, 64)
|
||||
out.write(item.encryptionKey, 1, 64)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
Encode.int32(item.behaviorBitfield, buffer)
|
||||
buffer.put(item.signingKey, 1, 64)
|
||||
buffer.put(item.encryptionKey, 1, 64)
|
||||
}
|
||||
|
||||
override fun writeBytesToSign(out: OutputStream) {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
override fun writeUnencrypted(out: OutputStream) {
|
||||
write(out)
|
||||
}
|
||||
|
||||
override fun writeUnencrypted(buffer: ByteBuffer) {
|
||||
write(buffer)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
Encode.int32(behaviorBitfield, buffer)
|
||||
buffer.put(signingKey, 1, 64)
|
||||
buffer.put(encryptionKey, 1, 64)
|
||||
}
|
||||
|
||||
class Builder {
|
||||
@ -80,13 +107,13 @@ open class V2Pubkey constructor(version: Long, override val stream: Long, overri
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic fun read(`in`: InputStream, stream: Long): V2Pubkey {
|
||||
@JvmStatic fun read(input: InputStream, stream: Long): V2Pubkey {
|
||||
return V2Pubkey(
|
||||
version = 2,
|
||||
stream = stream,
|
||||
behaviorBitfield = Decode.uint32(`in`).toInt(),
|
||||
signingKey = Decode.bytes(`in`, 64),
|
||||
encryptionKey = Decode.bytes(`in`, 64)
|
||||
behaviorBitfield = Decode.uint32(input).toInt(),
|
||||
signingKey = Decode.bytes(input, 64),
|
||||
encryptionKey = Decode.bytes(input, 64)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
package ch.dissem.bitmessage.entity.payload
|
||||
|
||||
import ch.dissem.bitmessage.entity.EncryptedStreamableWriter
|
||||
import ch.dissem.bitmessage.utils.Decode
|
||||
import ch.dissem.bitmessage.utils.Encode
|
||||
import java.io.InputStream
|
||||
@ -34,32 +35,8 @@ class V3Pubkey protected constructor(
|
||||
override var signature: ByteArray? = null
|
||||
) : V2Pubkey(version, stream, behaviorBitfield, signingKey, encryptionKey) {
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
writeBytesToSign(out)
|
||||
Encode.varBytes(
|
||||
signature ?: throw IllegalStateException("signature not available"),
|
||||
out
|
||||
)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
super.write(buffer)
|
||||
Encode.varInt(nonceTrialsPerByte, buffer)
|
||||
Encode.varInt(extraBytes, buffer)
|
||||
Encode.varBytes(
|
||||
signature ?: throw IllegalStateException("signature not available"),
|
||||
buffer
|
||||
)
|
||||
}
|
||||
|
||||
override val isSigned: Boolean = true
|
||||
|
||||
override fun writeBytesToSign(out: OutputStream) {
|
||||
super.write(out)
|
||||
Encode.varInt(nonceTrialsPerByte, out)
|
||||
Encode.varInt(extraBytes, out)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is V3Pubkey) return false
|
||||
@ -75,6 +52,37 @@ class V3Pubkey protected constructor(
|
||||
return Objects.hash(nonceTrialsPerByte, extraBytes)
|
||||
}
|
||||
|
||||
override fun writer(): EncryptedStreamableWriter = Writer(this)
|
||||
|
||||
protected open class Writer(
|
||||
private val item: V3Pubkey
|
||||
) : V2Pubkey.Writer(item) {
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
writeBytesToSign(out)
|
||||
Encode.varBytes(
|
||||
item.signature ?: throw IllegalStateException("signature not available"),
|
||||
out
|
||||
)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
super.write(buffer)
|
||||
Encode.varInt(item.nonceTrialsPerByte, buffer)
|
||||
Encode.varInt(item.extraBytes, buffer)
|
||||
Encode.varBytes(
|
||||
item.signature ?: throw IllegalStateException("signature not available"),
|
||||
buffer
|
||||
)
|
||||
}
|
||||
|
||||
override fun writeBytesToSign(out: OutputStream) {
|
||||
super.write(out)
|
||||
Encode.varInt(item.nonceTrialsPerByte, out)
|
||||
Encode.varInt(item.extraBytes, out)
|
||||
}
|
||||
}
|
||||
|
||||
class Builder {
|
||||
private var streamNumber: Long = 0
|
||||
private var behaviorBitfield: Int = 0
|
||||
@ -134,16 +142,16 @@ class V3Pubkey protected constructor(
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic fun read(`is`: InputStream, stream: Long): V3Pubkey {
|
||||
@JvmStatic fun read(input: InputStream, stream: Long): V3Pubkey {
|
||||
return V3Pubkey(
|
||||
version = 3,
|
||||
stream = stream,
|
||||
behaviorBitfield = Decode.int32(`is`),
|
||||
signingKey = Decode.bytes(`is`, 64),
|
||||
encryptionKey = Decode.bytes(`is`, 64),
|
||||
nonceTrialsPerByte = Decode.varInt(`is`),
|
||||
extraBytes = Decode.varInt(`is`),
|
||||
signature = Decode.varBytes(`is`)
|
||||
behaviorBitfield = Decode.int32(input),
|
||||
signingKey = Decode.bytes(input, 64),
|
||||
encryptionKey = Decode.bytes(input, 64),
|
||||
nonceTrialsPerByte = Decode.varInt(input),
|
||||
extraBytes = Decode.varInt(input),
|
||||
signature = Decode.varBytes(input)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ package ch.dissem.bitmessage.entity.payload
|
||||
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||
import ch.dissem.bitmessage.entity.Plaintext
|
||||
|
||||
import ch.dissem.bitmessage.entity.SignedStreamableWriter
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
@ -38,22 +38,28 @@ open class V4Broadcast : Broadcast {
|
||||
throw IllegalArgumentException("Address version 3 or older expected, but was " + senderAddress.version)
|
||||
}
|
||||
|
||||
override fun writer(): SignedStreamableWriter = Writer(this)
|
||||
|
||||
override fun writeBytesToSign(out: OutputStream) {
|
||||
plaintext?.write(out, false) ?: throw IllegalStateException("no plaintext data available")
|
||||
}
|
||||
protected open class Writer(
|
||||
private val item: V4Broadcast
|
||||
) : SignedStreamableWriter {
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
encrypted?.write(out) ?: throw IllegalStateException("broadcast not encrypted")
|
||||
}
|
||||
override fun writeBytesToSign(out: OutputStream) {
|
||||
item.plaintext?.writer(false)?.write(out) ?: throw IllegalStateException("no plaintext data available")
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
encrypted?.write(buffer) ?: throw IllegalStateException("broadcast not encrypted")
|
||||
override fun write(out: OutputStream) {
|
||||
item.encrypted?.writer()?.write(out) ?: throw IllegalStateException("broadcast not encrypted")
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
item.encrypted?.writer()?.write(buffer) ?: throw IllegalStateException("broadcast not encrypted")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic fun read(`in`: InputStream, stream: Long, length: Int): V4Broadcast {
|
||||
return V4Broadcast(4, stream, CryptoBox.read(`in`, length), null)
|
||||
}
|
||||
@JvmStatic
|
||||
fun read(input: InputStream, stream: Long, length: Int) =
|
||||
V4Broadcast(4, stream, CryptoBox.read(input, length), null)
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ package ch.dissem.bitmessage.entity.payload
|
||||
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||
import ch.dissem.bitmessage.entity.Encrypted
|
||||
import ch.dissem.bitmessage.entity.EncryptedStreamableWriter
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
||||
import ch.dissem.bitmessage.utils.Decode
|
||||
import java.io.InputStream
|
||||
@ -63,29 +64,6 @@ class V4Pubkey : Pubkey, Encrypted {
|
||||
override val isDecrypted: Boolean
|
||||
get() = decrypted != null
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
out.write(tag)
|
||||
encrypted?.write(out) ?: throw IllegalStateException("pubkey is encrypted")
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
buffer.put(tag)
|
||||
encrypted?.write(buffer) ?: throw IllegalStateException("pubkey is encrypted")
|
||||
}
|
||||
|
||||
override fun writeUnencrypted(out: OutputStream) {
|
||||
decrypted?.write(out) ?: throw IllegalStateException("pubkey is encrypted")
|
||||
}
|
||||
|
||||
override fun writeUnencrypted(buffer: ByteBuffer) {
|
||||
decrypted?.write(buffer) ?: throw IllegalStateException("pubkey is encrypted")
|
||||
}
|
||||
|
||||
override fun writeBytesToSign(out: OutputStream) {
|
||||
out.write(tag)
|
||||
decrypted?.writeBytesToSign(out) ?: throw IllegalStateException("pubkey is encrypted")
|
||||
}
|
||||
|
||||
override val signingKey: ByteArray
|
||||
get() = decrypted?.signingKey ?: throw IllegalStateException("pubkey is encrypted")
|
||||
|
||||
@ -126,14 +104,45 @@ class V4Pubkey : Pubkey, Encrypted {
|
||||
return result
|
||||
}
|
||||
|
||||
override fun writer(): EncryptedStreamableWriter = Writer(this)
|
||||
|
||||
private class Writer(
|
||||
val item: V4Pubkey
|
||||
) : EncryptedStreamableWriter {
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
out.write(item.tag)
|
||||
item.encrypted?.writer()?.write(out) ?: throw IllegalStateException("pubkey is encrypted")
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
buffer.put(item.tag)
|
||||
item.encrypted?.writer()?.write(buffer) ?: throw IllegalStateException("pubkey is encrypted")
|
||||
}
|
||||
|
||||
override fun writeUnencrypted(out: OutputStream) {
|
||||
item.decrypted?.writer()?.write(out) ?: throw IllegalStateException("pubkey is encrypted")
|
||||
}
|
||||
|
||||
override fun writeUnencrypted(buffer: ByteBuffer) {
|
||||
item.decrypted?.writer()?.write(buffer) ?: throw IllegalStateException("pubkey is encrypted")
|
||||
}
|
||||
|
||||
override fun writeBytesToSign(out: OutputStream) {
|
||||
out.write(item.tag)
|
||||
item.decrypted?.writer()?.writeBytesToSign(out) ?: throw IllegalStateException("pubkey is encrypted")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic fun read(`in`: InputStream, stream: Long, length: Int, encrypted: Boolean): V4Pubkey {
|
||||
if (encrypted)
|
||||
return V4Pubkey(stream,
|
||||
Decode.bytes(`in`, 32),
|
||||
CryptoBox.read(`in`, length - 32))
|
||||
else
|
||||
return V4Pubkey(V3Pubkey.read(`in`, stream))
|
||||
@JvmStatic
|
||||
fun read(input: InputStream, stream: Long, length: Int, encrypted: Boolean) = if (encrypted) {
|
||||
V4Pubkey(stream,
|
||||
Decode.bytes(input, 32),
|
||||
CryptoBox.read(input, length - 32))
|
||||
} else {
|
||||
V4Pubkey(V3Pubkey.read(input, stream))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ package ch.dissem.bitmessage.entity.payload
|
||||
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||
import ch.dissem.bitmessage.entity.Plaintext
|
||||
import ch.dissem.bitmessage.entity.SignedStreamableWriter
|
||||
import ch.dissem.bitmessage.utils.Decode
|
||||
|
||||
import java.io.InputStream
|
||||
@ -40,19 +41,27 @@ class V5Broadcast : V4Broadcast {
|
||||
this.tag = senderAddress.tag ?: throw IllegalStateException("version 4 address without tag")
|
||||
}
|
||||
|
||||
override fun writeBytesToSign(out: OutputStream) {
|
||||
out.write(tag)
|
||||
super.writeBytesToSign(out)
|
||||
}
|
||||
override fun writer(): SignedStreamableWriter = Writer(this)
|
||||
|
||||
private class Writer(
|
||||
private val item: V5Broadcast
|
||||
) : V4Broadcast.Writer(item) {
|
||||
|
||||
override fun writeBytesToSign(out: OutputStream) {
|
||||
out.write(item.tag)
|
||||
super.writeBytesToSign(out)
|
||||
}
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
out.write(item.tag)
|
||||
super.write(out)
|
||||
}
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
out.write(tag)
|
||||
super.write(out)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic fun read(`is`: InputStream, stream: Long, length: Int): V5Broadcast {
|
||||
return V5Broadcast(stream, Decode.bytes(`is`, 32), CryptoBox.read(`is`, length - 32))
|
||||
}
|
||||
@JvmStatic
|
||||
fun read(input: InputStream, stream: Long, length: Int) =
|
||||
V5Broadcast(stream, Decode.bytes(input, 32), CryptoBox.read(input, length - 32))
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
package ch.dissem.bitmessage.entity.valueobject
|
||||
|
||||
import ch.dissem.bitmessage.entity.Streamable
|
||||
import ch.dissem.bitmessage.entity.StreamableWriter
|
||||
import ch.dissem.bitmessage.utils.Strings
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
@ -39,20 +40,29 @@ data class InventoryVector constructor(
|
||||
return Arrays.hashCode(hash)
|
||||
}
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
out.write(hash)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
buffer.put(hash)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return Strings.hex(hash)
|
||||
}
|
||||
|
||||
override fun writer(): StreamableWriter = Writer(this)
|
||||
|
||||
private class Writer(
|
||||
private val item: InventoryVector
|
||||
) : StreamableWriter {
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
out.write(item.hash)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
buffer.put(item.hash)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic fun fromHash(hash: ByteArray?): InventoryVector? {
|
||||
@JvmStatic
|
||||
fun fromHash(hash: ByteArray?): InventoryVector? {
|
||||
return InventoryVector(
|
||||
hash ?: return null
|
||||
)
|
||||
|
@ -17,6 +17,7 @@
|
||||
package ch.dissem.bitmessage.entity.valueobject
|
||||
|
||||
import ch.dissem.bitmessage.entity.Streamable
|
||||
import ch.dissem.bitmessage.entity.StreamableWriter
|
||||
import ch.dissem.bitmessage.entity.Version
|
||||
import ch.dissem.bitmessage.utils.Encode
|
||||
import ch.dissem.bitmessage.utils.UnixTime
|
||||
@ -77,9 +78,7 @@ data class NetworkAddress(
|
||||
|
||||
fun provides(service: Version.Service?): Boolean = service?.isEnabled(services) ?: false
|
||||
|
||||
fun toInetAddress(): InetAddress {
|
||||
return InetAddress.getByAddress(IPv6)
|
||||
}
|
||||
fun toInetAddress() = InetAddress.getByAddress(IPv6)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
@ -98,32 +97,40 @@ data class NetworkAddress(
|
||||
return "[" + toInetAddress() + "]:" + port
|
||||
}
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
write(out, false)
|
||||
}
|
||||
fun writer(light: Boolean): StreamableWriter = Writer(
|
||||
item = this,
|
||||
light = light
|
||||
)
|
||||
|
||||
fun write(out: OutputStream, light: Boolean) {
|
||||
if (!light) {
|
||||
Encode.int64(time, out)
|
||||
Encode.int32(stream, out)
|
||||
override fun writer(): StreamableWriter = Writer(
|
||||
item = this
|
||||
)
|
||||
|
||||
private class Writer(
|
||||
private val item: NetworkAddress,
|
||||
private val light: Boolean = false
|
||||
) : StreamableWriter {
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
if (!light) {
|
||||
Encode.int64(item.time, out)
|
||||
Encode.int32(item.stream, out)
|
||||
}
|
||||
Encode.int64(item.services, out)
|
||||
out.write(item.IPv6)
|
||||
Encode.int16(item.port, out)
|
||||
}
|
||||
Encode.int64(services, out)
|
||||
out.write(IPv6)
|
||||
Encode.int16(port, out)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
write(buffer, false)
|
||||
}
|
||||
|
||||
fun write(buffer: ByteBuffer, light: Boolean) {
|
||||
if (!light) {
|
||||
Encode.int64(time, buffer)
|
||||
Encode.int32(stream, buffer)
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
if (!light) {
|
||||
Encode.int64(item.time, buffer)
|
||||
Encode.int32(item.stream, buffer)
|
||||
}
|
||||
Encode.int64(item.services, buffer)
|
||||
buffer.put(item.IPv6)
|
||||
Encode.int16(item.port, buffer)
|
||||
}
|
||||
Encode.int64(services, buffer)
|
||||
buffer.put(IPv6)
|
||||
Encode.int16(port, buffer)
|
||||
|
||||
}
|
||||
|
||||
class Builder {
|
||||
@ -194,6 +201,7 @@ data class NetworkAddress(
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmField val ANY = NetworkAddress(time = 0, stream = 0, services = 0, IPv6 = ByteArray(16), port = 0)
|
||||
@JvmField
|
||||
val ANY = NetworkAddress(time = 0, stream = 0, services = 0, IPv6 = ByteArray(16), port = 0)
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ package ch.dissem.bitmessage.entity.valueobject
|
||||
import ch.dissem.bitmessage.InternalContext
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||
import ch.dissem.bitmessage.entity.Streamable
|
||||
import ch.dissem.bitmessage.entity.StreamableWriter
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey
|
||||
import ch.dissem.bitmessage.exception.ApplicationException
|
||||
import ch.dissem.bitmessage.factory.Factory
|
||||
@ -66,7 +67,52 @@ data class PrivateKey(
|
||||
builder.nonceTrialsPerByte, builder.extraBytes, *builder.features)
|
||||
)
|
||||
|
||||
private class Builder internal constructor(internal val version: Long, internal val stream: Long, internal val shorter: Boolean) {
|
||||
override fun equals(other: Any?) = other is PrivateKey
|
||||
&& Arrays.equals(privateEncryptionKey, other.privateEncryptionKey)
|
||||
&& Arrays.equals(privateSigningKey, other.privateSigningKey)
|
||||
&& pubkey == other.pubkey
|
||||
|
||||
override fun hashCode() = pubkey.hashCode()
|
||||
|
||||
override fun writer(): StreamableWriter = Writer(this)
|
||||
|
||||
private class Writer(
|
||||
private val item: PrivateKey
|
||||
) : StreamableWriter {
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
Encode.varInt(item.pubkey.version, out)
|
||||
Encode.varInt(item.pubkey.stream, out)
|
||||
val baos = ByteArrayOutputStream()
|
||||
item.pubkey.writer().writeUnencrypted(baos)
|
||||
Encode.varInt(baos.size(), out)
|
||||
out.write(baos.toByteArray())
|
||||
Encode.varBytes(item.privateSigningKey, out)
|
||||
Encode.varBytes(item.privateEncryptionKey, out)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
Encode.varInt(item.pubkey.version, buffer)
|
||||
Encode.varInt(item.pubkey.stream, buffer)
|
||||
try {
|
||||
val baos = ByteArrayOutputStream()
|
||||
item.pubkey.writer().writeUnencrypted(baos)
|
||||
Encode.varBytes(baos.toByteArray(), buffer)
|
||||
} catch (e: IOException) {
|
||||
throw ApplicationException(e)
|
||||
}
|
||||
|
||||
Encode.varBytes(item.privateSigningKey, buffer)
|
||||
Encode.varBytes(item.privateEncryptionKey, buffer)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class Builder internal constructor(
|
||||
internal val version: Long,
|
||||
internal val stream: Long,
|
||||
internal val shorter: Boolean
|
||||
) {
|
||||
|
||||
internal var seed: ByteArray? = null
|
||||
internal var nextNonce: Long = 0
|
||||
@ -129,44 +175,12 @@ data class PrivateKey(
|
||||
}
|
||||
}
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
Encode.varInt(pubkey.version, out)
|
||||
Encode.varInt(pubkey.stream, out)
|
||||
val baos = ByteArrayOutputStream()
|
||||
pubkey.writeUnencrypted(baos)
|
||||
Encode.varInt(baos.size(), out)
|
||||
out.write(baos.toByteArray())
|
||||
Encode.varBytes(privateSigningKey, out)
|
||||
Encode.varBytes(privateEncryptionKey, out)
|
||||
}
|
||||
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
Encode.varInt(pubkey.version, buffer)
|
||||
Encode.varInt(pubkey.stream, buffer)
|
||||
try {
|
||||
val baos = ByteArrayOutputStream()
|
||||
pubkey.writeUnencrypted(baos)
|
||||
Encode.varBytes(baos.toByteArray(), buffer)
|
||||
} catch (e: IOException) {
|
||||
throw ApplicationException(e)
|
||||
}
|
||||
|
||||
Encode.varBytes(privateSigningKey, buffer)
|
||||
Encode.varBytes(privateEncryptionKey, buffer)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?) = other is PrivateKey
|
||||
&& Arrays.equals(privateEncryptionKey, other.privateEncryptionKey)
|
||||
&& Arrays.equals(privateSigningKey, other.privateSigningKey)
|
||||
&& pubkey == other.pubkey
|
||||
|
||||
override fun hashCode() = pubkey.hashCode()
|
||||
|
||||
companion object {
|
||||
@JvmField val PRIVATE_KEY_SIZE = 32
|
||||
@JvmField
|
||||
val PRIVATE_KEY_SIZE = 32
|
||||
|
||||
@JvmStatic fun deterministic(passphrase: String, numberOfAddresses: Int, version: Long, stream: Long, shorter: Boolean): List<PrivateKey> {
|
||||
@JvmStatic
|
||||
fun deterministic(passphrase: String, numberOfAddresses: Int, version: Long, stream: Long, shorter: Boolean): List<PrivateKey> {
|
||||
val result = ArrayList<PrivateKey>(numberOfAddresses)
|
||||
val builder = Builder(version, stream, shorter).seed(passphrase)
|
||||
for (i in 0..numberOfAddresses - 1) {
|
||||
@ -176,13 +190,14 @@ data class PrivateKey(
|
||||
return result
|
||||
}
|
||||
|
||||
@JvmStatic fun read(`is`: InputStream): PrivateKey {
|
||||
val version = Decode.varInt(`is`).toInt()
|
||||
val stream = Decode.varInt(`is`)
|
||||
val len = Decode.varInt(`is`).toInt()
|
||||
val pubkey = Factory.readPubkey(version.toLong(), stream, `is`, len, false) ?: throw ApplicationException("Unknown pubkey version encountered")
|
||||
val signingKey = Decode.varBytes(`is`)
|
||||
val encryptionKey = Decode.varBytes(`is`)
|
||||
@JvmStatic
|
||||
fun read(input: InputStream): PrivateKey {
|
||||
val version = Decode.varInt(input).toInt()
|
||||
val stream = Decode.varInt(input)
|
||||
val len = Decode.varInt(input).toInt()
|
||||
val pubkey = Factory.readPubkey(version.toLong(), stream, input, len, false) ?: throw ApplicationException("Unknown pubkey version encountered")
|
||||
val signingKey = Decode.varBytes(input)
|
||||
val encryptionKey = Decode.varBytes(input)
|
||||
return PrivateKey(signingKey, encryptionKey, pubkey)
|
||||
}
|
||||
}
|
||||
|
@ -37,33 +37,33 @@ import java.util.*
|
||||
data class Message constructor(
|
||||
val subject: String,
|
||||
val body: String,
|
||||
val parents: List<InventoryVector>,
|
||||
val files: List<Attachment>
|
||||
val parents: List<InventoryVector> = emptyList(),
|
||||
val files: List<Attachment> = emptyList()
|
||||
) : ExtendedEncoding.ExtendedType {
|
||||
|
||||
override val type: String = TYPE
|
||||
|
||||
override fun pack(): MPMap<MPString, MPType<*>> {
|
||||
val result = MPMap<MPString, MPType<*>>()
|
||||
result.put(mp(""), mp(TYPE))
|
||||
result.put(mp("subject"), mp(subject))
|
||||
result.put(mp("body"), mp(body))
|
||||
result["".mp] = TYPE.mp
|
||||
result["subject".mp] = subject.mp
|
||||
result["body".mp] = body.mp
|
||||
|
||||
if (!files.isEmpty()) {
|
||||
val items = MPArray<MPMap<MPString, MPType<*>>>()
|
||||
result.put(mp("files"), items)
|
||||
result["files".mp] = items
|
||||
for (file in files) {
|
||||
val item = MPMap<MPString, MPType<*>>()
|
||||
item.put(mp("name"), mp(file.name))
|
||||
item.put(mp("data"), mp(*file.data))
|
||||
item.put(mp("type"), mp(file.type))
|
||||
item.put(mp("disposition"), mp(file.disposition.name))
|
||||
item["name".mp] = file.name.mp
|
||||
item["data".mp] = file.data.mp
|
||||
item["type".mp] = file.type.mp
|
||||
item["disposition".mp] = file.disposition.name.mp
|
||||
items.add(item)
|
||||
}
|
||||
}
|
||||
if (!parents.isEmpty()) {
|
||||
val items = MPArray<MPBinary>()
|
||||
result.put(mp("parents"), items)
|
||||
result["parents".mp] = items
|
||||
for ((hash) in parents) {
|
||||
items.add(mp(*hash))
|
||||
}
|
||||
@ -139,26 +139,26 @@ data class Message constructor(
|
||||
override val type: String = TYPE
|
||||
|
||||
override fun unpack(map: MPMap<MPString, MPType<*>>): Message {
|
||||
val subject = str(map[mp("subject")]) ?: ""
|
||||
val body = str(map[mp("body")]) ?: ""
|
||||
val subject = str(map["subject".mp]) ?: ""
|
||||
val body = str(map["body".mp]) ?: ""
|
||||
val parents = LinkedList<InventoryVector>()
|
||||
val files = LinkedList<Attachment>()
|
||||
val mpParents = map[mp("parents")] as? MPArray<*>
|
||||
val mpParents = map["parents".mp] as? MPArray<*>
|
||||
for (parent in mpParents ?: emptyList<MPArray<MPBinary>>()) {
|
||||
parents.add(InventoryVector.fromHash(
|
||||
(parent as? MPBinary)?.value ?: continue
|
||||
) ?: continue)
|
||||
}
|
||||
val mpFiles = map[mp("files")] as? MPArray<*>
|
||||
val mpFiles = map["files".mp] as? MPArray<*>
|
||||
for (item in mpFiles ?: emptyList<Any>()) {
|
||||
if (item is MPMap<*, *>) {
|
||||
val b = Attachment.Builder()
|
||||
b.name(str(item[mp("name")])!!)
|
||||
b.name(str(item["name".mp])!!)
|
||||
b.data(
|
||||
bin(item[mp("data")] ?: continue) ?: continue
|
||||
bin(item["data".mp] ?: continue) ?: continue
|
||||
)
|
||||
b.type(str(item[mp("type")])!!)
|
||||
val disposition = str(item[mp("disposition")])
|
||||
b.type(str(item["type".mp])!!)
|
||||
val disposition = str(item["disposition".mp])
|
||||
if ("inline" == disposition) {
|
||||
b.inline()
|
||||
} else if ("attachment" == disposition) {
|
||||
@ -179,6 +179,6 @@ data class Message constructor(
|
||||
companion object {
|
||||
private val LOG = LoggerFactory.getLogger(Message::class.java)
|
||||
|
||||
val TYPE = "message"
|
||||
const val TYPE = "message"
|
||||
}
|
||||
}
|
||||
|
@ -35,9 +35,9 @@ data class Vote constructor(val msgId: InventoryVector, val vote: String) : Exte
|
||||
|
||||
override fun pack(): MPMap<MPString, MPType<*>> {
|
||||
val result = MPMap<MPString, MPType<*>>()
|
||||
result.put(mp(""), mp(TYPE))
|
||||
result.put(mp("msgId"), mp(*msgId.hash))
|
||||
result.put(mp("vote"), mp(vote))
|
||||
result.put("".mp, TYPE.mp)
|
||||
result.put("msgId".mp, msgId.hash.mp)
|
||||
result.put("vote".mp, vote.mp)
|
||||
return result
|
||||
}
|
||||
|
||||
@ -77,13 +77,14 @@ data class Vote constructor(val msgId: InventoryVector, val vote: String) : Exte
|
||||
get() = TYPE
|
||||
|
||||
override fun unpack(map: MPMap<MPString, MPType<*>>): Vote {
|
||||
val msgId = InventoryVector.fromHash((map[mp("msgId")] as? MPBinary)?.value) ?: throw IllegalArgumentException("data doesn't contain proper msgId")
|
||||
val vote = str(map[mp("vote")]) ?: throw IllegalArgumentException("no vote given")
|
||||
val msgId = InventoryVector.fromHash((map["msgId".mp] as? MPBinary)?.value) ?: throw IllegalArgumentException("data doesn't contain proper msgId")
|
||||
val vote = str(map["vote".mp]) ?: throw IllegalArgumentException("no vote given")
|
||||
return Vote(msgId, vote)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmField val TYPE = "vote"
|
||||
@JvmField
|
||||
val TYPE = "vote"
|
||||
}
|
||||
}
|
||||
|
@ -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? {
|
||||
try {
|
||||
InflaterInputStream(ByteArrayInputStream(zippedData)).use { unzipper ->
|
||||
val reader = Reader.getInstance()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val map = reader.read(unzipper) as MPMap<MPString, MPType<*>>
|
||||
val map = Reader.read(unzipper) as MPMap<MPString, MPType<*>>
|
||||
val messageType = map[KEY_MESSAGE_TYPE]
|
||||
if (messageType == null) {
|
||||
LOG.error("Missing message type")
|
||||
|
@ -156,11 +156,11 @@ object Factory {
|
||||
return GetPubkey.read(stream, streamNumber, length, version)
|
||||
}
|
||||
|
||||
@JvmStatic fun readPubkey(version: Long, stream: Long, `is`: InputStream, length: Int, encrypted: Boolean): Pubkey? {
|
||||
@JvmStatic fun readPubkey(version: Long, stream: Long, input: InputStream, length: Int, encrypted: Boolean): Pubkey? {
|
||||
when (version.toInt()) {
|
||||
2 -> return V2Pubkey.read(`is`, stream)
|
||||
3 -> return V3Pubkey.read(`is`, stream)
|
||||
4 -> return V4Pubkey.read(`is`, stream, length, encrypted)
|
||||
2 -> return V2Pubkey.read(input, stream)
|
||||
3 -> return V3Pubkey.read(input, stream)
|
||||
4 -> return V4Pubkey.read(input, stream, length, encrypted)
|
||||
}
|
||||
LOG.debug("Unexpected pubkey version $version, handling as generic payload object")
|
||||
return null
|
||||
|
@ -39,130 +39,88 @@ object V3MessageFactory {
|
||||
private val LOG = LoggerFactory.getLogger(V3MessageFactory::class.java)
|
||||
|
||||
@JvmStatic
|
||||
fun read(`in`: InputStream): NetworkMessage? {
|
||||
findMagic(`in`)
|
||||
val command = getCommand(`in`)
|
||||
val length = Decode.uint32(`in`).toInt()
|
||||
fun read(input: InputStream): NetworkMessage? {
|
||||
findMagic(input)
|
||||
val command = getCommand(input)
|
||||
val length = Decode.uint32(input).toInt()
|
||||
if (length > 1600003) {
|
||||
throw NodeException("Payload of $length bytes received, no more than 1600003 was expected.")
|
||||
}
|
||||
val checksum = Decode.bytes(`in`, 4)
|
||||
val checksum = Decode.bytes(input, 4)
|
||||
|
||||
val payloadBytes = Decode.bytes(`in`, length)
|
||||
val payloadBytes = Decode.bytes(input, length)
|
||||
|
||||
if (testChecksum(checksum, payloadBytes)) {
|
||||
val payload = getPayload(command, ByteArrayInputStream(payloadBytes), length)
|
||||
if (payload != null)
|
||||
return NetworkMessage(payload)
|
||||
else
|
||||
return null
|
||||
return payload?.let { NetworkMessage(payload) }
|
||||
} else {
|
||||
throw IOException("Checksum failed for message '$command'")
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getPayload(command: String, stream: InputStream, length: Int): MessagePayload? {
|
||||
when (command) {
|
||||
"version" -> return parseVersion(stream)
|
||||
"verack" -> return VerAck()
|
||||
"addr" -> return parseAddr(stream)
|
||||
"inv" -> return parseInv(stream)
|
||||
"getdata" -> return parseGetData(stream)
|
||||
"object" -> return readObject(stream, length)
|
||||
"custom" -> return readCustom(stream, length)
|
||||
else -> {
|
||||
LOG.debug("Unknown command: " + command)
|
||||
return null
|
||||
}
|
||||
fun getPayload(command: String, stream: InputStream, length: Int): MessagePayload? = when (command) {
|
||||
"version" -> parseVersion(stream)
|
||||
"verack" -> VerAck()
|
||||
"addr" -> Addr(parseList(stream) { parseAddress(it, false) })
|
||||
"inv" -> Inv(parseList(stream) { parseInventoryVector(it) })
|
||||
"getdata" -> GetData(parseList(stream) { parseInventoryVector(it) })
|
||||
"object" -> readObject(stream, length)
|
||||
"custom" -> readCustom(stream, length)
|
||||
else -> {
|
||||
LOG.debug("Unknown command: $command")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun readCustom(`in`: InputStream, length: Int): MessagePayload {
|
||||
return CustomMessage.read(`in`, length)
|
||||
}
|
||||
private fun readCustom(input: InputStream, length: Int): MessagePayload = CustomMessage.read(input, length)
|
||||
|
||||
@JvmStatic
|
||||
fun readObject(`in`: InputStream, length: Int): ObjectMessage {
|
||||
fun readObject(input: InputStream, length: Int): ObjectMessage {
|
||||
val counter = AccessCounter()
|
||||
val nonce = Decode.bytes(`in`, 8, counter)
|
||||
val expiresTime = Decode.int64(`in`, counter)
|
||||
val objectType = Decode.uint32(`in`, counter)
|
||||
val version = Decode.varInt(`in`, counter)
|
||||
val stream = Decode.varInt(`in`, counter)
|
||||
val nonce = Decode.bytes(input, 8, counter)
|
||||
val expiresTime = Decode.int64(input, counter)
|
||||
val objectType = Decode.uint32(input, counter)
|
||||
val version = Decode.varInt(input, counter)
|
||||
val stream = Decode.varInt(input, counter)
|
||||
|
||||
val data = Decode.bytes(`in`, length - counter.length())
|
||||
var payload: ObjectPayload
|
||||
try {
|
||||
val dataStream = ByteArrayInputStream(data)
|
||||
payload = Factory.getObjectPayload(objectType, version, stream, dataStream, data.size)
|
||||
val data = Decode.bytes(input, length - counter.length())
|
||||
val payload: ObjectPayload = try {
|
||||
Factory.getObjectPayload(objectType, version, stream, ByteArrayInputStream(data), data.size)
|
||||
} catch (e: Exception) {
|
||||
if (LOG.isTraceEnabled) {
|
||||
LOG.trace("Could not parse object payload - using generic payload instead", e)
|
||||
LOG.trace(Strings.hex(data))
|
||||
}
|
||||
payload = GenericPayload(version, stream, data)
|
||||
GenericPayload(version, stream, data)
|
||||
}
|
||||
|
||||
return ObjectMessage.Builder()
|
||||
.nonce(nonce)
|
||||
.expiresTime(expiresTime)
|
||||
.objectType(objectType)
|
||||
.stream(stream)
|
||||
.payload(payload)
|
||||
.build()
|
||||
return ObjectMessage(
|
||||
nonce, expiresTime, payload, objectType, version, stream
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseGetData(stream: InputStream): GetData {
|
||||
private fun <T> parseList(stream: InputStream, reader: (InputStream) -> (T)): List<T> {
|
||||
val count = Decode.varInt(stream)
|
||||
val inventoryVectors = LinkedList<InventoryVector>()
|
||||
for (i in 0..count - 1) {
|
||||
inventoryVectors.add(parseInventoryVector(stream))
|
||||
val items = LinkedList<T>()
|
||||
for (i in 0 until count) {
|
||||
items.add(reader(stream))
|
||||
}
|
||||
return GetData(inventoryVectors)
|
||||
return items
|
||||
}
|
||||
|
||||
private fun parseInv(stream: InputStream): Inv {
|
||||
val count = Decode.varInt(stream)
|
||||
val inventoryVectors = LinkedList<InventoryVector>()
|
||||
for (i in 0..count - 1) {
|
||||
inventoryVectors.add(parseInventoryVector(stream))
|
||||
}
|
||||
return Inv(inventoryVectors)
|
||||
}
|
||||
private fun parseVersion(stream: InputStream) = Version(
|
||||
version = Decode.int32(stream),
|
||||
services = Decode.int64(stream),
|
||||
timestamp = Decode.int64(stream),
|
||||
addrRecv = parseAddress(stream, true),
|
||||
addrFrom = parseAddress(stream, true),
|
||||
nonce = Decode.int64(stream),
|
||||
userAgent = Decode.varString(stream),
|
||||
streams = Decode.varIntList(stream)
|
||||
)
|
||||
|
||||
private fun parseAddr(stream: InputStream): Addr {
|
||||
val count = Decode.varInt(stream)
|
||||
val networkAddresses = LinkedList<NetworkAddress>()
|
||||
for (i in 0..count - 1) {
|
||||
networkAddresses.add(parseAddress(stream, false))
|
||||
}
|
||||
return Addr(networkAddresses)
|
||||
}
|
||||
|
||||
private fun parseVersion(stream: InputStream): Version {
|
||||
val version = Decode.int32(stream)
|
||||
val services = Decode.int64(stream)
|
||||
val timestamp = Decode.int64(stream)
|
||||
val addrRecv = parseAddress(stream, true)
|
||||
val addrFrom = parseAddress(stream, true)
|
||||
val nonce = Decode.int64(stream)
|
||||
val userAgent = Decode.varString(stream)
|
||||
val streamNumbers = Decode.varIntList(stream)
|
||||
|
||||
return Version.Builder()
|
||||
.version(version)
|
||||
.services(services)
|
||||
.timestamp(timestamp)
|
||||
.addrRecv(addrRecv).addrFrom(addrFrom)
|
||||
.nonce(nonce)
|
||||
.userAgent(userAgent)
|
||||
.streams(*streamNumbers).build()
|
||||
}
|
||||
|
||||
private fun parseInventoryVector(stream: InputStream): InventoryVector {
|
||||
return InventoryVector(Decode.bytes(stream, 32))
|
||||
}
|
||||
private fun parseInventoryVector(stream: InputStream) = InventoryVector(Decode.bytes(stream, 32))
|
||||
|
||||
private fun parseAddress(stream: InputStream, light: Boolean): NetworkAddress {
|
||||
val time: Long
|
||||
@ -177,23 +135,15 @@ object V3MessageFactory {
|
||||
val services = Decode.int64(stream)
|
||||
val ipv6 = Decode.bytes(stream, 16)
|
||||
val port = Decode.uint16(stream)
|
||||
return NetworkAddress.Builder()
|
||||
.time(time)
|
||||
.stream(streamNumber)
|
||||
.services(services)
|
||||
.ipv6(ipv6)
|
||||
.port(port)
|
||||
.build()
|
||||
|
||||
return NetworkAddress(
|
||||
time, streamNumber, services, ipv6, port
|
||||
)
|
||||
}
|
||||
|
||||
private fun testChecksum(checksum: ByteArray, payload: ByteArray): Boolean {
|
||||
val payloadChecksum = cryptography().sha512(payload)
|
||||
for (i in checksum.indices) {
|
||||
if (checksum[i] != payloadChecksum[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
return checksum.indices.none { checksum[it] != payloadChecksum[it] }
|
||||
}
|
||||
|
||||
private fun getCommand(stream: InputStream): String {
|
||||
@ -210,10 +160,10 @@ object V3MessageFactory {
|
||||
return String(bytes, 0, end, Charsets.US_ASCII)
|
||||
}
|
||||
|
||||
private fun findMagic(`in`: InputStream) {
|
||||
private fun findMagic(input: InputStream) {
|
||||
var pos = 0
|
||||
for (i in 0..1619999) {
|
||||
val b = `in`.read().toByte()
|
||||
val b = input.read().toByte()
|
||||
if (b == NetworkMessage.MAGIC_BYTES[pos]) {
|
||||
if (pos + 1 == NetworkMessage.MAGIC_BYTES.size) {
|
||||
return
|
||||
|
@ -30,8 +30,7 @@ import java.util.*
|
||||
* Similar to the [V3MessageFactory], but used for NIO buffers which may or may not contain a whole message.
|
||||
*/
|
||||
class V3MessageReader {
|
||||
private var headerBuffer: ByteBuffer? = null
|
||||
private var dataBuffer: ByteBuffer? = null
|
||||
val buffer: ByteBuffer = ByteBuffer.allocate(MAX_PAYLOAD_SIZE)
|
||||
|
||||
private var state: ReaderState? = ReaderState.MAGIC
|
||||
private var command: String? = null
|
||||
@ -40,89 +39,83 @@ class V3MessageReader {
|
||||
|
||||
private val messages = LinkedList<NetworkMessage>()
|
||||
|
||||
fun getActiveBuffer(): ByteBuffer {
|
||||
if (state != null && state != ReaderState.DATA) {
|
||||
if (headerBuffer == null) {
|
||||
headerBuffer = BufferPool.allocateHeaderBuffer()
|
||||
}
|
||||
}
|
||||
return if (state == ReaderState.DATA)
|
||||
dataBuffer ?: throw IllegalStateException("data buffer is null")
|
||||
else
|
||||
headerBuffer ?: throw IllegalStateException("header buffer is null")
|
||||
}
|
||||
|
||||
fun update() {
|
||||
if (state != ReaderState.DATA) {
|
||||
getActiveBuffer() // in order to initialize
|
||||
headerBuffer?.flip() ?: throw IllegalStateException("header buffer is null")
|
||||
buffer.flip()
|
||||
}
|
||||
when (state) {
|
||||
V3MessageReader.ReaderState.MAGIC -> magic(headerBuffer ?: throw IllegalStateException("header buffer is null"))
|
||||
V3MessageReader.ReaderState.HEADER -> header(headerBuffer ?: throw IllegalStateException("header buffer is null"))
|
||||
V3MessageReader.ReaderState.DATA -> data(dataBuffer ?: throw IllegalStateException("data buffer is null"))
|
||||
var s = when (state) {
|
||||
ReaderState.MAGIC -> magic()
|
||||
ReaderState.HEADER -> header()
|
||||
ReaderState.DATA -> data()
|
||||
else -> ReaderState.WAIT_FOR_DATA
|
||||
}
|
||||
while (s != ReaderState.WAIT_FOR_DATA) {
|
||||
s = when (state) {
|
||||
ReaderState.MAGIC -> magic()
|
||||
ReaderState.HEADER -> header()
|
||||
ReaderState.DATA -> data(flip = false)
|
||||
else -> ReaderState.WAIT_FOR_DATA
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun magic(headerBuffer: ByteBuffer) {
|
||||
if (!findMagicBytes(headerBuffer)) {
|
||||
headerBuffer.compact()
|
||||
return
|
||||
} else {
|
||||
state = ReaderState.HEADER
|
||||
header(headerBuffer)
|
||||
}
|
||||
private fun magic(): ReaderState = if (!findMagicBytes(buffer)) {
|
||||
buffer.compact()
|
||||
ReaderState.WAIT_FOR_DATA
|
||||
} else {
|
||||
state = ReaderState.HEADER
|
||||
ReaderState.HEADER
|
||||
}
|
||||
|
||||
private fun header(headerBuffer: ByteBuffer) {
|
||||
if (headerBuffer.remaining() < 20) {
|
||||
headerBuffer.compact()
|
||||
headerBuffer.limit(20)
|
||||
return
|
||||
private fun header(): ReaderState {
|
||||
if (buffer.remaining() < 20) {
|
||||
buffer.compact()
|
||||
return ReaderState.WAIT_FOR_DATA
|
||||
}
|
||||
command = getCommand(headerBuffer)
|
||||
length = Decode.uint32(headerBuffer).toInt()
|
||||
command = getCommand(buffer)
|
||||
length = Decode.uint32(buffer).toInt()
|
||||
if (length > MAX_PAYLOAD_SIZE) {
|
||||
throw NodeException("Payload of " + length + " bytes received, no more than " +
|
||||
MAX_PAYLOAD_SIZE + " was expected.")
|
||||
throw NodeException(
|
||||
"Payload of " + length + " bytes received, no more than " +
|
||||
MAX_PAYLOAD_SIZE + " was expected."
|
||||
)
|
||||
}
|
||||
headerBuffer.get(checksum)
|
||||
buffer.get(checksum)
|
||||
state = ReaderState.DATA
|
||||
this.headerBuffer = null
|
||||
BufferPool.deallocate(headerBuffer)
|
||||
val dataBuffer = BufferPool.allocate(length)
|
||||
this.dataBuffer = dataBuffer
|
||||
dataBuffer.clear()
|
||||
dataBuffer.limit(length)
|
||||
data(dataBuffer)
|
||||
return ReaderState.DATA
|
||||
}
|
||||
|
||||
private fun data(dataBuffer: ByteBuffer) {
|
||||
if (dataBuffer.position() < length) {
|
||||
return
|
||||
} else {
|
||||
dataBuffer.flip()
|
||||
private fun data(flip: Boolean = true): ReaderState {
|
||||
if (flip) {
|
||||
if (buffer.position() < length) {
|
||||
return ReaderState.WAIT_FOR_DATA
|
||||
} else {
|
||||
buffer.flip()
|
||||
}
|
||||
} else if (buffer.remaining() < length) {
|
||||
buffer.compact()
|
||||
return ReaderState.WAIT_FOR_DATA
|
||||
}
|
||||
if (!testChecksum(dataBuffer)) {
|
||||
if (!testChecksum(buffer)) {
|
||||
state = ReaderState.MAGIC
|
||||
this.dataBuffer = null
|
||||
BufferPool.deallocate(dataBuffer)
|
||||
buffer.clear()
|
||||
throw NodeException("Checksum failed for message '$command'")
|
||||
}
|
||||
try {
|
||||
V3MessageFactory.getPayload(
|
||||
command ?: throw IllegalStateException("command is null"),
|
||||
ByteArrayInputStream(dataBuffer.array(),
|
||||
dataBuffer.arrayOffset() + dataBuffer.position(), length),
|
||||
ByteArrayInputStream(
|
||||
buffer.array(),
|
||||
buffer.arrayOffset() + buffer.position(), length
|
||||
),
|
||||
length
|
||||
)?.let { messages.add(NetworkMessage(it)) }
|
||||
} catch (e: IOException) {
|
||||
throw NodeException(e.message)
|
||||
} finally {
|
||||
state = ReaderState.MAGIC
|
||||
this.dataBuffer = null
|
||||
BufferPool.deallocate(dataBuffer)
|
||||
}
|
||||
return ReaderState.MAGIC
|
||||
}
|
||||
|
||||
fun getMessages(): MutableList<NetworkMessage> {
|
||||
@ -163,8 +156,10 @@ class V3MessageReader {
|
||||
}
|
||||
|
||||
private fun testChecksum(buffer: ByteBuffer): Boolean {
|
||||
val payloadChecksum = cryptography().sha512(buffer.array(),
|
||||
buffer.arrayOffset() + buffer.position(), length)
|
||||
val payloadChecksum = cryptography().sha512(
|
||||
buffer.array(),
|
||||
buffer.arrayOffset() + buffer.position(), length
|
||||
)
|
||||
for (i in checksum.indices) {
|
||||
if (checksum[i] != payloadChecksum[i]) {
|
||||
return false
|
||||
@ -173,17 +168,7 @@ class V3MessageReader {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* De-allocates all buffers. This method should be called iff the reader isn't used anymore, i.e. when its
|
||||
* connection is severed.
|
||||
*/
|
||||
fun cleanup() {
|
||||
state = null
|
||||
headerBuffer?.let { BufferPool.deallocate(it) }
|
||||
dataBuffer?.let { BufferPool.deallocate(it) }
|
||||
}
|
||||
|
||||
private enum class ReaderState {
|
||||
MAGIC, HEADER, DATA
|
||||
MAGIC, HEADER, DATA, WAIT_FOR_DATA
|
||||
}
|
||||
}
|
||||
|
@ -36,12 +36,16 @@ import javax.crypto.spec.SecretKeySpec
|
||||
/**
|
||||
* Implements everything that isn't directly dependent on either Spongy- or Bouncycastle.
|
||||
*/
|
||||
abstract class AbstractCryptography protected constructor(@JvmField protected val provider: Provider) : Cryptography, InternalContext.ContextHolder {
|
||||
abstract class AbstractCryptography protected constructor(@JvmField protected val provider: Provider) : Cryptography,
|
||||
InternalContext.ContextHolder {
|
||||
private lateinit var ctx: InternalContext
|
||||
|
||||
@JvmField protected val ALGORITHM_ECDSA = "ECDSA"
|
||||
@JvmField protected val ALGORITHM_ECDSA_SHA1 = "SHA1withECDSA"
|
||||
@JvmField protected val ALGORITHM_EVP_SHA256 = "SHA256withECDSA"
|
||||
@JvmField
|
||||
protected val ALGORITHM_ECDSA = "ECDSA"
|
||||
@JvmField
|
||||
protected val ALGORITHM_ECDSA_SHA1 = "SHA1withECDSA"
|
||||
@JvmField
|
||||
protected val ALGORITHM_EVP_SHA256 = "SHA256withECDSA"
|
||||
|
||||
override fun setContext(context: InternalContext) {
|
||||
ctx = context
|
||||
@ -65,8 +69,12 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va
|
||||
return mda.digest(mda.digest())
|
||||
}
|
||||
|
||||
override fun doubleSha512(data: ByteArray, length: Int): ByteArray {
|
||||
val mda = md("SHA-512")
|
||||
override fun doubleSha512(data: ByteArray, length: Int) = doubleHash("SHA-512", data, length);
|
||||
|
||||
override fun doubleSha256(data: ByteArray, length: Int) = doubleHash("SHA-256", data, length);
|
||||
|
||||
private fun doubleHash(method: String, data: ByteArray, length: Int): ByteArray {
|
||||
val mda = md(method)
|
||||
mda.update(data, 0, length)
|
||||
return mda.digest(mda.digest())
|
||||
}
|
||||
@ -75,12 +83,6 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va
|
||||
return hash("RIPEMD160", *data)
|
||||
}
|
||||
|
||||
override fun doubleSha256(data: ByteArray, length: Int): ByteArray {
|
||||
val mda = md("SHA-256")
|
||||
mda.update(data, 0, length)
|
||||
return mda.digest(mda.digest())
|
||||
}
|
||||
|
||||
override fun sha1(vararg data: ByteArray): ByteArray {
|
||||
return hash("SHA-1", *data)
|
||||
}
|
||||
@ -91,13 +93,17 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va
|
||||
return result
|
||||
}
|
||||
|
||||
override fun doProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long,
|
||||
extraBytes: Long, callback: ProofOfWorkEngine.Callback) {
|
||||
override fun doProofOfWork(
|
||||
objectMessage: ObjectMessage, nonceTrialsPerByte: Long,
|
||||
extraBytes: Long, callback: ProofOfWorkEngine.Callback
|
||||
) {
|
||||
|
||||
val initialHash = getInitialHash(objectMessage)
|
||||
|
||||
val target = getProofOfWorkTarget(objectMessage,
|
||||
max(nonceTrialsPerByte, NETWORK_NONCE_TRIALS_PER_BYTE), max(extraBytes, NETWORK_EXTRA_BYTES))
|
||||
val target = getProofOfWorkTarget(
|
||||
objectMessage,
|
||||
max(nonceTrialsPerByte, NETWORK_NONCE_TRIALS_PER_BYTE), max(extraBytes, NETWORK_EXTRA_BYTES)
|
||||
)
|
||||
|
||||
ctx.proofOfWorkEngine.calculateNonce(initialHash, target, callback)
|
||||
}
|
||||
@ -105,7 +111,10 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va
|
||||
@Throws(InsufficientProofOfWorkException::class)
|
||||
override fun checkProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) {
|
||||
val target = getProofOfWorkTarget(objectMessage, nonceTrialsPerByte, extraBytes)
|
||||
val value = doubleSha512(objectMessage.nonce ?: throw ApplicationException("Object without nonce"), getInitialHash(objectMessage))
|
||||
val value = doubleSha512(
|
||||
objectMessage.nonce ?: throw ApplicationException("Object without nonce"),
|
||||
getInitialHash(objectMessage)
|
||||
)
|
||||
if (Bytes.lt(target, value, 8)) {
|
||||
throw InsufficientProofOfWorkException(target, value)
|
||||
}
|
||||
@ -136,7 +145,11 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va
|
||||
return sha512(objectMessage.payloadBytesWithoutNonce)
|
||||
}
|
||||
|
||||
override fun getProofOfWorkTarget(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long): ByteArray {
|
||||
override fun getProofOfWorkTarget(
|
||||
objectMessage: ObjectMessage,
|
||||
nonceTrialsPerByte: Long,
|
||||
extraBytes: Long
|
||||
): ByteArray {
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val nonceTrialsPerByte = if (nonceTrialsPerByte == 0L) NETWORK_NONCE_TRIALS_PER_BYTE else nonceTrialsPerByte
|
||||
@Suppress("NAME_SHADOWING")
|
||||
@ -181,12 +194,16 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va
|
||||
|
||||
}
|
||||
|
||||
override fun createPubkey(version: Long, stream: Long, privateSigningKey: ByteArray, privateEncryptionKey: ByteArray,
|
||||
nonceTrialsPerByte: Long, extraBytes: Long, vararg features: Pubkey.Feature): Pubkey {
|
||||
return Factory.createPubkey(version, stream,
|
||||
override fun createPubkey(
|
||||
version: Long, stream: Long, privateSigningKey: ByteArray, privateEncryptionKey: ByteArray,
|
||||
nonceTrialsPerByte: Long, extraBytes: Long, vararg features: Pubkey.Feature
|
||||
): Pubkey {
|
||||
return Factory.createPubkey(
|
||||
version, stream,
|
||||
createPublicKey(privateSigningKey),
|
||||
createPublicKey(privateEncryptionKey),
|
||||
nonceTrialsPerByte, extraBytes, *features)
|
||||
nonceTrialsPerByte, extraBytes, *features
|
||||
)
|
||||
}
|
||||
|
||||
override fun keyToBigInt(privateKey: ByteArray): BigInteger {
|
||||
|
@ -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.valueobject.InventoryVector
|
||||
import ch.dissem.bitmessage.entity.valueobject.Label
|
||||
import ch.dissem.bitmessage.exception.ApplicationException
|
||||
import ch.dissem.bitmessage.utils.SqlStrings.join
|
||||
import ch.dissem.bitmessage.utils.Collections.single
|
||||
import ch.dissem.bitmessage.utils.Strings
|
||||
import ch.dissem.bitmessage.utils.UnixTime
|
||||
import java.util.*
|
||||
@ -110,27 +109,8 @@ abstract class AbstractMessageRepository : MessageRepository, InternalContext.Co
|
||||
return find("iv IN (SELECT child FROM Message_Parent WHERE parent=X'${Strings.hex(parent.inventoryVector!!.hash)}')")
|
||||
}
|
||||
|
||||
override fun getConversation(conversationId: UUID): List<Plaintext> {
|
||||
return find("conversation=X'${conversationId.toString().replace("-", "")}'")
|
||||
}
|
||||
|
||||
override fun getLabels(): List<Label> {
|
||||
return findLabels("1=1")
|
||||
}
|
||||
|
||||
override fun getLabels(vararg types: Label.Type): List<Label> {
|
||||
return findLabels("type IN (${join(*types)})")
|
||||
}
|
||||
|
||||
protected abstract fun findLabels(where: String): List<Label>
|
||||
|
||||
|
||||
protected fun <T> single(collection: Collection<T>): T? {
|
||||
return when (collection.size) {
|
||||
0 -> null
|
||||
1 -> collection.iterator().next()
|
||||
else -> throw ApplicationException("This shouldn't happen, found ${collection.size} items, one or none was expected")
|
||||
}
|
||||
override fun getConversation(conversationId: UUID, offset: Int, limit: Int): List<Plaintext> {
|
||||
return find("conversation=X'${conversationId.toString().replace("-", "")}'", offset, limit)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -156,6 +156,16 @@ interface Cryptography {
|
||||
fun doProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long,
|
||||
extraBytes: Long, callback: ProofOfWorkEngine.Callback)
|
||||
|
||||
@JvmSynthetic
|
||||
fun doProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long,
|
||||
extraBytes: Long, callback: (ByteArray, ByteArray) -> Unit) {
|
||||
doProofOfWork(objectMessage, nonceTrialsPerByte, extraBytes, object : ProofOfWorkEngine.Callback {
|
||||
override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) {
|
||||
callback.invoke(initialHash, nonce)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param objectMessage to be checked
|
||||
* *
|
||||
|
@ -35,9 +35,9 @@ open class DefaultLabeler : Labeler, InternalContext.ContextHolder {
|
||||
msg.status = RECEIVED
|
||||
val labelsToAdd =
|
||||
if (msg.type == BROADCAST) {
|
||||
ctx.messageRepository.getLabels(Label.Type.BROADCAST, Label.Type.UNREAD)
|
||||
ctx.labelRepository.getLabels(Label.Type.BROADCAST, Label.Type.UNREAD)
|
||||
} else {
|
||||
ctx.messageRepository.getLabels(Label.Type.INBOX, Label.Type.UNREAD)
|
||||
ctx.labelRepository.getLabels(Label.Type.INBOX, Label.Type.UNREAD)
|
||||
}
|
||||
msg.addLabels(labelsToAdd)
|
||||
listener?.invoke(msg, labelsToAdd, emptyList())
|
||||
@ -45,7 +45,7 @@ open class DefaultLabeler : Labeler, InternalContext.ContextHolder {
|
||||
|
||||
override fun markAsDraft(msg: Plaintext) {
|
||||
msg.status = DRAFT
|
||||
val labelsToAdd = ctx.messageRepository.getLabels(Label.Type.DRAFT)
|
||||
val labelsToAdd = ctx.labelRepository.getLabels(Label.Type.DRAFT)
|
||||
msg.addLabels(labelsToAdd)
|
||||
listener?.invoke(msg, labelsToAdd, emptyList())
|
||||
}
|
||||
@ -58,7 +58,7 @@ open class DefaultLabeler : Labeler, InternalContext.ContextHolder {
|
||||
}
|
||||
val labelsToRemove = msg.labels.filter { it.type == Label.Type.DRAFT }
|
||||
msg.removeLabel(Label.Type.DRAFT)
|
||||
val labelsToAdd = ctx.messageRepository.getLabels(Label.Type.OUTBOX)
|
||||
val labelsToAdd = ctx.labelRepository.getLabels(Label.Type.OUTBOX)
|
||||
msg.addLabels(labelsToAdd)
|
||||
listener?.invoke(msg, labelsToAdd, labelsToRemove)
|
||||
}
|
||||
@ -67,7 +67,7 @@ open class DefaultLabeler : Labeler, InternalContext.ContextHolder {
|
||||
msg.status = SENT
|
||||
val labelsToRemove = msg.labels.filter { it.type == Label.Type.OUTBOX }
|
||||
msg.removeLabel(Label.Type.OUTBOX)
|
||||
val labelsToAdd = ctx.messageRepository.getLabels(Label.Type.SENT)
|
||||
val labelsToAdd = ctx.labelRepository.getLabels(Label.Type.SENT)
|
||||
msg.addLabels(labelsToAdd)
|
||||
listener?.invoke(msg, labelsToAdd, labelsToRemove)
|
||||
}
|
||||
@ -83,7 +83,7 @@ open class DefaultLabeler : Labeler, InternalContext.ContextHolder {
|
||||
}
|
||||
|
||||
override fun markAsUnread(msg: Plaintext) {
|
||||
val labelsToAdd = ctx.messageRepository.getLabels(Label.Type.UNREAD)
|
||||
val labelsToAdd = ctx.labelRepository.getLabels(Label.Type.UNREAD)
|
||||
msg.addLabels(labelsToAdd)
|
||||
listener?.invoke(msg, labelsToAdd, emptyList())
|
||||
}
|
||||
@ -91,7 +91,7 @@ open class DefaultLabeler : Labeler, InternalContext.ContextHolder {
|
||||
override fun delete(msg: Plaintext) {
|
||||
val labelsToRemove = msg.labels.toSet()
|
||||
msg.labels.clear()
|
||||
val labelsToAdd = ctx.messageRepository.getLabels(Label.Type.TRASH)
|
||||
val labelsToAdd = ctx.labelRepository.getLabels(Label.Type.TRASH)
|
||||
msg.addLabels(labelsToAdd)
|
||||
listener?.invoke(msg, labelsToAdd, labelsToRemove)
|
||||
}
|
||||
|
@ -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.*
|
||||
|
||||
interface MessageRepository {
|
||||
fun getLabels(): List<Label>
|
||||
|
||||
fun getLabels(vararg types: Label.Type): List<Label>
|
||||
|
||||
fun save(label: Label)
|
||||
|
||||
fun countUnread(label: Label?): Int
|
||||
|
||||
fun getAllMessages(): List<Plaintext>
|
||||
@ -47,7 +41,7 @@ interface MessageRepository {
|
||||
* *
|
||||
* @return a distinct list of all conversations that have at least one message with the given label.
|
||||
*/
|
||||
fun findConversations(label: Label?): List<UUID>
|
||||
fun findConversations(label: Label?, offset: Int = 0, limit: Int = 0): List<UUID>
|
||||
|
||||
fun findMessages(label: Label?): List<Plaintext>
|
||||
|
||||
@ -74,5 +68,5 @@ interface MessageRepository {
|
||||
* *
|
||||
* @return all messages with the given conversation ID
|
||||
*/
|
||||
fun getConversation(conversationId: UUID): Collection<Plaintext>
|
||||
fun getConversation(conversationId: UUID, offset: Int = 0, limit: Int = 0): Collection<Plaintext>
|
||||
}
|
||||
|
@ -31,4 +31,13 @@ interface NodeRegistry {
|
||||
fun getKnownAddresses(limit: Int, vararg streams: Long): List<NetworkAddress>
|
||||
|
||||
fun offerAddresses(nodes: List<NetworkAddress>)
|
||||
|
||||
fun update(node: NetworkAddress)
|
||||
|
||||
fun remove(node: NetworkAddress)
|
||||
|
||||
/**
|
||||
* Remove stale nodes
|
||||
*/
|
||||
fun cleanup()
|
||||
}
|
||||
|
@ -31,8 +31,8 @@ object NodeRegistryHelper {
|
||||
|
||||
@JvmStatic
|
||||
fun loadStableNodes(): Map<Long, Set<NetworkAddress>> {
|
||||
javaClass.classLoader.getResourceAsStream("nodes.txt").use { `in` ->
|
||||
val scanner = Scanner(`in`)
|
||||
javaClass.classLoader.getResourceAsStream("nodes.txt").use { input ->
|
||||
val scanner = Scanner(input)
|
||||
var stream: Long = 0
|
||||
val result = HashMap<Long, Set<NetworkAddress>>()
|
||||
var streamSet: MutableSet<NetworkAddress>? = null
|
||||
@ -41,7 +41,7 @@ object NodeRegistryHelper {
|
||||
val line = scanner.nextLine().trim { it <= ' ' }
|
||||
if (line.startsWith("[stream")) {
|
||||
stream = java.lang.Long.parseLong(line.substring(8, line.lastIndexOf(']')))
|
||||
streamSet = HashSet<NetworkAddress>()
|
||||
streamSet = HashSet()
|
||||
result.put(stream, streamSet)
|
||||
} else if (streamSet != null && !line.isEmpty() && !line.startsWith("#")) {
|
||||
val portIndex = line.lastIndexOf(':')
|
||||
|
@ -23,16 +23,32 @@ interface ProofOfWorkEngine {
|
||||
/**
|
||||
* Returns a nonce, such that the first 8 bytes from sha512(sha512(nonce||initialHash)) represent a unsigned long
|
||||
* smaller than target.
|
||||
|
||||
*
|
||||
* @param initialHash the SHA-512 hash of the object to send, sans nonce
|
||||
* *
|
||||
* @param target the target, representing an unsigned long
|
||||
* *
|
||||
* @param callback called with the calculated nonce as argument. The ProofOfWorkEngine implementation must make
|
||||
* * sure this is only called once.
|
||||
* @param callback called with the initial hash and the calculated nonce as argument. The ProofOfWorkEngine
|
||||
* implementation must make sure this is only called once.
|
||||
*/
|
||||
fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: Callback)
|
||||
|
||||
/**
|
||||
* Returns a nonce, such that the first 8 bytes from sha512(sha512(nonce||initialHash)) represent a unsigned long
|
||||
* smaller than target.
|
||||
*
|
||||
* @param initialHash the SHA-512 hash of the object to send, sans nonce
|
||||
* @param target the target, representing an unsigned long
|
||||
* @param callback called with the initial hash and the calculated nonce as argument. The ProofOfWorkEngine
|
||||
* implementation must make sure this is only called once.
|
||||
*/
|
||||
@JvmSynthetic
|
||||
fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: (ByteArray, ByteArray) -> Unit) {
|
||||
calculateNonce(initialHash, target, object : Callback {
|
||||
override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) {
|
||||
callback.invoke(initialHash, nonce)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
/**
|
||||
* @param nonce 8 bytes nonce
|
||||
|
@ -641,24 +641,26 @@ private class Encoder(val options: Options, output: ByteArray) : Coder(output) {
|
||||
* Lookup table for turning Base64 alphabet positions (6 bits)
|
||||
* into output bytes.
|
||||
*/
|
||||
private val ENCODE = charArrayOf(
|
||||
private val ENCODE = charsAsBytes(
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
|
||||
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
|
||||
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
|
||||
).map { it.toByte() }.toByteArray()
|
||||
)
|
||||
|
||||
/**
|
||||
* Lookup table for turning Base64 alphabet positions (6 bits)
|
||||
* into output bytes.
|
||||
*/
|
||||
private val ENCODE_WEBSAFE = charArrayOf(
|
||||
private val ENCODE_WEBSAFE = charsAsBytes(
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
|
||||
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
|
||||
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'
|
||||
).map { it.toByte() }.toByteArray()
|
||||
)
|
||||
|
||||
private fun charsAsBytes(vararg elements: Char): ByteArray = elements.map { it.toByte() }.toByteArray()
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
package ch.dissem.bitmessage.utils
|
||||
|
||||
import ch.dissem.bitmessage.exception.ApplicationException
|
||||
import java.util.*
|
||||
|
||||
object Collections {
|
||||
@ -67,4 +68,12 @@ object Collections {
|
||||
}
|
||||
throw IllegalArgumentException("Empty collection? Size: " + collection.size)
|
||||
}
|
||||
|
||||
@JvmStatic fun <T> single(collection: Collection<T>): T? {
|
||||
return when (collection.size) {
|
||||
0 -> null
|
||||
1 -> collection.iterator().next()
|
||||
else -> throw ApplicationException("This shouldn't happen, found ${collection.size} items, one or none was expected")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,10 +16,11 @@
|
||||
|
||||
package ch.dissem.bitmessage.utils
|
||||
|
||||
import ch.dissem.bitmessage.entity.Conversation
|
||||
import ch.dissem.bitmessage.entity.Plaintext
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
||||
import ch.dissem.bitmessage.entity.valueobject.Label
|
||||
import ch.dissem.bitmessage.ports.MessageRepository
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.util.*
|
||||
import java.util.Collections
|
||||
import java.util.regex.Pattern
|
||||
@ -30,7 +31,11 @@ import java.util.regex.Pattern.CASE_INSENSITIVE
|
||||
*/
|
||||
class ConversationService(private val messageRepository: MessageRepository) {
|
||||
|
||||
private val SUBJECT_PREFIX = Pattern.compile("^(re|fwd?):", CASE_INSENSITIVE)
|
||||
private val SUBJECT_PREFIX = Pattern.compile("^(re|fwd?):\\s*", CASE_INSENSITIVE)
|
||||
|
||||
fun findConversations(label: Label?, offset: Int = 0, limit: Int = 0, conversationLimit: Int = 10) =
|
||||
messageRepository.findConversations(label, offset, limit)
|
||||
.map { getConversation(it, conversationLimit) }
|
||||
|
||||
/**
|
||||
* Retrieve the whole conversation from one single message. If the message isn't part
|
||||
@ -41,7 +46,7 @@ class ConversationService(private val messageRepository: MessageRepository) {
|
||||
* *
|
||||
* @return a list of messages that belong to the same conversation.
|
||||
*/
|
||||
fun getConversation(message: Plaintext): List<Plaintext> {
|
||||
fun getConversation(message: Plaintext): Conversation {
|
||||
return getConversation(message.conversationId)
|
||||
}
|
||||
|
||||
@ -58,8 +63,8 @@ class ConversationService(private val messageRepository: MessageRepository) {
|
||||
return result
|
||||
}
|
||||
|
||||
fun getConversation(conversationId: UUID): List<Plaintext> {
|
||||
val messages = sorted(messageRepository.getConversation(conversationId))
|
||||
fun getConversation(conversationId: UUID, limit: Int = 0): Conversation {
|
||||
val messages = sorted(messageRepository.getConversation(conversationId, 0, limit))
|
||||
val map = HashMap<InventoryVector, Plaintext>(messages.size)
|
||||
for (message in messages) {
|
||||
message.inventoryVector?.let {
|
||||
@ -74,7 +79,7 @@ class ConversationService(private val messageRepository: MessageRepository) {
|
||||
result.add(pos, last)
|
||||
addAncestors(last, result, messages, map)
|
||||
}
|
||||
return result
|
||||
return Conversation(conversationId, getSubject(result) ?: "", result)
|
||||
}
|
||||
|
||||
fun getSubject(conversation: List<Plaintext>): String? {
|
||||
@ -109,7 +114,12 @@ class ConversationService(private val messageRepository: MessageRepository) {
|
||||
return child.parents.firstOrNull { it == item.inventoryVector } != null
|
||||
}
|
||||
|
||||
private fun addAncestors(message: Plaintext, result: LinkedList<Plaintext>, messages: LinkedList<Plaintext>, map: MutableMap<InventoryVector, Plaintext>) {
|
||||
private fun addAncestors(
|
||||
message: Plaintext,
|
||||
result: LinkedList<Plaintext>,
|
||||
messages: LinkedList<Plaintext>,
|
||||
map: MutableMap<InventoryVector, Plaintext>
|
||||
) {
|
||||
for (parentKey in message.parents) {
|
||||
map.remove(parentKey)?.let {
|
||||
messages.remove(it)
|
||||
|
@ -29,7 +29,7 @@ object DebugUtils {
|
||||
try {
|
||||
val f = File(System.getProperty("user.home") + "/jabit.error/" + objectMessage.inventoryVector + ".inv")
|
||||
f.createNewFile()
|
||||
objectMessage.write(FileOutputStream(f))
|
||||
objectMessage.writer().write(FileOutputStream(f))
|
||||
} catch (e: IOException) {
|
||||
LOG.debug(e.message, e)
|
||||
}
|
||||
|
@ -25,21 +25,21 @@ import java.nio.ByteBuffer
|
||||
* https://bitmessage.org/wiki/Protocol_specification#Common_structures
|
||||
*/
|
||||
object Decode {
|
||||
@JvmStatic fun shortVarBytes(`in`: InputStream, counter: AccessCounter): ByteArray {
|
||||
val length = uint16(`in`, counter)
|
||||
return bytes(`in`, length, counter)
|
||||
@JvmStatic fun shortVarBytes(input: InputStream, counter: AccessCounter): ByteArray {
|
||||
val length = uint16(input, counter)
|
||||
return bytes(input, length, counter)
|
||||
}
|
||||
|
||||
@JvmStatic @JvmOverloads fun varBytes(`in`: InputStream, counter: AccessCounter? = null): ByteArray {
|
||||
val length = varInt(`in`, counter).toInt()
|
||||
return bytes(`in`, length, counter)
|
||||
@JvmStatic @JvmOverloads fun varBytes(input: InputStream, counter: AccessCounter? = null): ByteArray {
|
||||
val length = varInt(input, counter).toInt()
|
||||
return bytes(input, length, counter)
|
||||
}
|
||||
|
||||
@JvmStatic @JvmOverloads fun bytes(`in`: InputStream, count: Int, counter: AccessCounter? = null): ByteArray {
|
||||
@JvmStatic @JvmOverloads fun bytes(input: InputStream, count: Int, counter: AccessCounter? = null): ByteArray {
|
||||
val result = ByteArray(count)
|
||||
var off = 0
|
||||
while (off < count) {
|
||||
val read = `in`.read(result, off, count - off)
|
||||
val read = input.read(result, off, count - off)
|
||||
if (read < 0) {
|
||||
throw IOException("Unexpected end of stream, wanted to read $count bytes but only got $off")
|
||||
}
|
||||
@ -49,60 +49,58 @@ object Decode {
|
||||
return result
|
||||
}
|
||||
|
||||
@JvmStatic fun varIntList(`in`: InputStream): LongArray {
|
||||
val length = varInt(`in`).toInt()
|
||||
@JvmStatic fun varIntList(input: InputStream): LongArray {
|
||||
val length = varInt(input).toInt()
|
||||
val result = LongArray(length)
|
||||
|
||||
for (i in 0..length - 1) {
|
||||
result[i] = varInt(`in`)
|
||||
for (i in 0 until length) {
|
||||
result[i] = varInt(input)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@JvmStatic @JvmOverloads fun varInt(`in`: InputStream, counter: AccessCounter? = null): Long {
|
||||
val first = `in`.read()
|
||||
@JvmStatic @JvmOverloads fun varInt(input: InputStream, counter: AccessCounter? = null): Long {
|
||||
val first = input.read()
|
||||
AccessCounter.inc(counter)
|
||||
when (first) {
|
||||
0xfd -> return uint16(`in`, counter).toLong()
|
||||
0xfe -> return uint32(`in`, counter)
|
||||
0xff -> return int64(`in`, counter)
|
||||
0xfd -> return uint16(input, counter).toLong()
|
||||
0xfe -> return uint32(input, counter)
|
||||
0xff -> return int64(input, counter)
|
||||
else -> return first.toLong()
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic fun uint8(`in`: InputStream): Int {
|
||||
return `in`.read()
|
||||
}
|
||||
@JvmStatic fun uint8(input: InputStream): Int = input.read()
|
||||
|
||||
@JvmStatic @JvmOverloads fun uint16(`in`: InputStream, counter: AccessCounter? = null): Int {
|
||||
@JvmStatic @JvmOverloads fun uint16(input: InputStream, counter: AccessCounter? = null): Int {
|
||||
AccessCounter.inc(counter, 2)
|
||||
return `in`.read() shl 8 or `in`.read()
|
||||
return input.read() shl 8 or input.read()
|
||||
}
|
||||
|
||||
@JvmStatic @JvmOverloads fun uint32(`in`: InputStream, counter: AccessCounter? = null): Long {
|
||||
@JvmStatic @JvmOverloads fun uint32(input: InputStream, counter: AccessCounter? = null): Long {
|
||||
AccessCounter.inc(counter, 4)
|
||||
return (`in`.read() shl 24 or (`in`.read() shl 16) or (`in`.read() shl 8) or `in`.read()).toLong()
|
||||
return (input.read() shl 24 or (input.read() shl 16) or (input.read() shl 8) or input.read()).toLong()
|
||||
}
|
||||
|
||||
@JvmStatic fun uint32(`in`: ByteBuffer): Long {
|
||||
return (u(`in`.get()) shl 24 or (u(`in`.get()) shl 16) or (u(`in`.get()) shl 8) or u(`in`.get())).toLong()
|
||||
@JvmStatic fun uint32(input: ByteBuffer): Long {
|
||||
return (u(input.get()) shl 24 or (u(input.get()) shl 16) or (u(input.get()) shl 8) or u(input.get())).toLong()
|
||||
}
|
||||
|
||||
@JvmStatic @JvmOverloads fun int32(`in`: InputStream, counter: AccessCounter? = null): Int {
|
||||
@JvmStatic @JvmOverloads fun int32(input: InputStream, counter: AccessCounter? = null): Int {
|
||||
AccessCounter.inc(counter, 4)
|
||||
return ByteBuffer.wrap(bytes(`in`, 4)).int
|
||||
return ByteBuffer.wrap(bytes(input, 4)).int
|
||||
}
|
||||
|
||||
@JvmStatic @JvmOverloads fun int64(`in`: InputStream, counter: AccessCounter? = null): Long {
|
||||
@JvmStatic @JvmOverloads fun int64(input: InputStream, counter: AccessCounter? = null): Long {
|
||||
AccessCounter.inc(counter, 8)
|
||||
return ByteBuffer.wrap(bytes(`in`, 8)).long
|
||||
return ByteBuffer.wrap(bytes(input, 8)).long
|
||||
}
|
||||
|
||||
@JvmStatic @JvmOverloads fun varString(`in`: InputStream, counter: AccessCounter? = null): String {
|
||||
val length = varInt(`in`, counter).toInt()
|
||||
@JvmStatic @JvmOverloads fun varString(input: InputStream, counter: AccessCounter? = null): String {
|
||||
val length = varInt(input, counter).toInt()
|
||||
// technically, it says the length in characters, but I think this one might be correct
|
||||
// otherwise it will get complicated, as we'll need to read UTF-8 char by char...
|
||||
return String(bytes(`in`, length, counter))
|
||||
return String(bytes(input, length, counter))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -26,53 +26,63 @@ import java.nio.ByteBuffer
|
||||
* https://bitmessage.org/wiki/Protocol_specification#Common_structures
|
||||
*/
|
||||
object Encode {
|
||||
@JvmStatic fun varIntList(values: LongArray, stream: OutputStream) {
|
||||
@JvmStatic
|
||||
fun varIntList(values: LongArray, stream: OutputStream) {
|
||||
varInt(values.size, stream)
|
||||
for (value in values) {
|
||||
varInt(value, stream)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic fun varIntList(values: LongArray, buffer: ByteBuffer) {
|
||||
@JvmStatic
|
||||
fun varIntList(values: LongArray, buffer: ByteBuffer) {
|
||||
varInt(values.size, buffer)
|
||||
for (value in values) {
|
||||
varInt(value, buffer)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic fun varInt(value: Int, buffer: ByteBuffer) = varInt(value.toLong(), buffer)
|
||||
@JvmStatic fun varInt(value: Long, buffer: ByteBuffer) {
|
||||
if (value < 0) {
|
||||
// This is due to the fact that Java doesn't really support unsigned values.
|
||||
// Please be aware that this might be an error due to a smaller negative value being cast to long.
|
||||
// Normally, negative values shouldn't occur within the protocol, and longs large enough for being
|
||||
// recognized as negatives aren't realistic.
|
||||
buffer.put(0xff.toByte())
|
||||
buffer.putLong(value)
|
||||
} else if (value < 0xfd) {
|
||||
buffer.put(value.toByte())
|
||||
} else if (value <= 0xffffL) {
|
||||
buffer.put(0xfd.toByte())
|
||||
buffer.putShort(value.toShort())
|
||||
} else if (value <= 0xffffffffL) {
|
||||
buffer.put(0xfe.toByte())
|
||||
buffer.putInt(value.toInt())
|
||||
} else {
|
||||
buffer.put(0xff.toByte())
|
||||
buffer.putLong(value)
|
||||
@JvmStatic
|
||||
fun varInt(value: Number, buffer: ByteBuffer) {
|
||||
val longValue = value.toLong()
|
||||
when {
|
||||
longValue < 0 -> {
|
||||
// This is due to the fact that Java doesn't really support unsigned values.
|
||||
// Please be aware that this might be an error due to a smaller negative value being cast to long.
|
||||
// Normally, negative values shouldn't occur within the protocol, and longs large enough for being
|
||||
// recognized as negatives aren't realistic.
|
||||
buffer.put(0xff.toByte())
|
||||
buffer.putLong(longValue)
|
||||
}
|
||||
longValue < 0xfd -> {
|
||||
buffer.put(value.toByte())
|
||||
}
|
||||
longValue <= 0xffffL -> {
|
||||
buffer.put(0xfd.toByte())
|
||||
buffer.putShort(value.toShort())
|
||||
}
|
||||
longValue <= 0xffffffffL -> {
|
||||
buffer.put(0xfe.toByte())
|
||||
buffer.putInt(value.toInt())
|
||||
}
|
||||
else -> {
|
||||
buffer.put(0xff.toByte())
|
||||
buffer.putLong(longValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic fun varInt(value: Int) = varInt(value.toLong())
|
||||
@JvmStatic fun varInt(value: Long): ByteArray {
|
||||
@JvmStatic
|
||||
fun varInt(value: Number): ByteArray {
|
||||
val buffer = ByteBuffer.allocate(9)
|
||||
varInt(value, buffer)
|
||||
buffer.flip()
|
||||
return Bytes.truncate(buffer.array(), buffer.limit())
|
||||
}
|
||||
|
||||
@JvmStatic @JvmOverloads fun varInt(value: Int, stream: OutputStream, counter: AccessCounter? = null) = varInt(value.toLong(), stream, counter)
|
||||
@JvmStatic @JvmOverloads fun varInt(value: Long, stream: OutputStream, counter: AccessCounter? = null) {
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun varInt(value: Number, stream: OutputStream, counter: AccessCounter? = null) {
|
||||
val buffer = ByteBuffer.allocate(9)
|
||||
varInt(value, buffer)
|
||||
buffer.flip()
|
||||
@ -80,46 +90,51 @@ object Encode {
|
||||
AccessCounter.inc(counter, buffer.limit())
|
||||
}
|
||||
|
||||
@JvmStatic @JvmOverloads fun int8(value: Long, stream: OutputStream, counter: AccessCounter? = null) = int8(value.toInt(), stream, counter)
|
||||
@JvmStatic @JvmOverloads fun int8(value: Int, stream: OutputStream, counter: AccessCounter? = null) {
|
||||
stream.write(value)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun int8(value: Number, stream: OutputStream, counter: AccessCounter? = null) {
|
||||
stream.write(value.toInt())
|
||||
AccessCounter.inc(counter)
|
||||
}
|
||||
|
||||
@JvmStatic @JvmOverloads fun int16(value: Long, stream: OutputStream, counter: AccessCounter? = null) = int16(value.toShort(), stream, counter)
|
||||
@JvmStatic @JvmOverloads fun int16(value: Int, stream: OutputStream, counter: AccessCounter? = null) = int16(value.toShort(), stream, counter)
|
||||
@JvmStatic @JvmOverloads fun int16(value: Short, stream: OutputStream, counter: AccessCounter? = null) {
|
||||
stream.write(ByteBuffer.allocate(2).putShort(value).array())
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun int16(value: Number, stream: OutputStream, counter: AccessCounter? = null) {
|
||||
stream.write(ByteBuffer.allocate(2).putShort(value.toShort()).array())
|
||||
AccessCounter.inc(counter, 2)
|
||||
}
|
||||
|
||||
@JvmStatic fun int16(value: Long, buffer: ByteBuffer) = int16(value.toShort(), buffer)
|
||||
@JvmStatic fun int16(value: Int, buffer: ByteBuffer) = int16(value.toShort(), buffer)
|
||||
@JvmStatic fun int16(value: Short, buffer: ByteBuffer) {
|
||||
buffer.putShort(value)
|
||||
@JvmStatic
|
||||
fun int16(value: Number, buffer: ByteBuffer) {
|
||||
buffer.putShort(value.toShort())
|
||||
}
|
||||
|
||||
@JvmStatic @JvmOverloads fun int32(value: Long, stream: OutputStream, counter: AccessCounter? = null) = int32(value.toInt(), stream, counter)
|
||||
@JvmStatic @JvmOverloads fun int32(value: Int, stream: OutputStream, counter: AccessCounter? = null) {
|
||||
stream.write(ByteBuffer.allocate(4).putInt(value).array())
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun int32(value: Number, stream: OutputStream, counter: AccessCounter? = null) {
|
||||
stream.write(ByteBuffer.allocate(4).putInt(value.toInt()).array())
|
||||
AccessCounter.inc(counter, 4)
|
||||
}
|
||||
|
||||
@JvmStatic fun int32(value: Long, buffer: ByteBuffer) = int32(value.toInt(), buffer)
|
||||
@JvmStatic fun int32(value: Int, buffer: ByteBuffer) {
|
||||
buffer.putInt(value)
|
||||
@JvmStatic
|
||||
fun int32(value: Number, buffer: ByteBuffer) {
|
||||
buffer.putInt(value.toInt())
|
||||
}
|
||||
|
||||
@JvmStatic @JvmOverloads fun int64(value: Long, stream: OutputStream, counter: AccessCounter? = null) {
|
||||
stream.write(ByteBuffer.allocate(8).putLong(value).array())
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun int64(value: Number, stream: OutputStream, counter: AccessCounter? = null) {
|
||||
stream.write(ByteBuffer.allocate(8).putLong(value.toLong()).array())
|
||||
AccessCounter.inc(counter, 8)
|
||||
}
|
||||
|
||||
@JvmStatic fun int64(value: Long, buffer: ByteBuffer) {
|
||||
buffer.putLong(value)
|
||||
@JvmStatic
|
||||
fun int64(value: Number, buffer: ByteBuffer) {
|
||||
buffer.putLong(value.toLong())
|
||||
}
|
||||
|
||||
@JvmStatic fun varString(value: String, out: OutputStream) {
|
||||
@JvmStatic
|
||||
fun varString(value: String, out: OutputStream) {
|
||||
val bytes = value.toByteArray(charset("utf-8"))
|
||||
// Technically, it says the length in characters, but I think this one might be correct.
|
||||
// It doesn't really matter, as only ASCII characters are being used.
|
||||
@ -128,7 +143,8 @@ object Encode {
|
||||
out.write(bytes)
|
||||
}
|
||||
|
||||
@JvmStatic fun varString(value: String, buffer: ByteBuffer) {
|
||||
@JvmStatic
|
||||
fun varString(value: String, buffer: ByteBuffer) {
|
||||
val bytes = value.toByteArray()
|
||||
// Technically, it says the length in characters, but I think this one might be correct.
|
||||
// It doesn't really matter, as only ASCII characters are being used.
|
||||
@ -137,12 +153,14 @@ object Encode {
|
||||
buffer.put(bytes)
|
||||
}
|
||||
|
||||
@JvmStatic fun varBytes(data: ByteArray, out: OutputStream) {
|
||||
@JvmStatic
|
||||
fun varBytes(data: ByteArray, out: OutputStream) {
|
||||
varInt(data.size.toLong(), out)
|
||||
out.write(data)
|
||||
}
|
||||
|
||||
@JvmStatic fun varBytes(data: ByteArray, buffer: ByteBuffer) {
|
||||
@JvmStatic
|
||||
fun varBytes(data: ByteArray, buffer: ByteBuffer) {
|
||||
varInt(data.size.toLong(), buffer)
|
||||
buffer.put(data)
|
||||
}
|
||||
@ -152,9 +170,10 @@ object Encode {
|
||||
* @param streamable the object to be serialized
|
||||
* @return an array of bytes representing the given streamable object.
|
||||
*/
|
||||
@JvmStatic fun bytes(streamable: Streamable): ByteArray {
|
||||
@JvmStatic
|
||||
fun bytes(streamable: Streamable): ByteArray {
|
||||
val stream = ByteArrayOutputStream()
|
||||
streamable.write(stream)
|
||||
streamable.writer().write(stream)
|
||||
return stream.toByteArray()
|
||||
}
|
||||
|
||||
@ -163,9 +182,10 @@ object Encode {
|
||||
* @param padding the result will be padded such that its length is a multiple of *padding*
|
||||
* @return the bytes of the given [Streamable] object, 0-padded such that the final length is x*padding.
|
||||
*/
|
||||
@JvmStatic fun bytes(streamable: Streamable, padding: Int): ByteArray {
|
||||
@JvmStatic
|
||||
fun bytes(streamable: Streamable, padding: Int): ByteArray {
|
||||
val stream = ByteArrayOutputStream()
|
||||
streamable.write(stream)
|
||||
streamable.writer().write(stream)
|
||||
val offset = padding - stream.size() % padding
|
||||
val length = stream.size() + offset
|
||||
val result = ByteArray(length)
|
||||
|
@ -35,11 +35,9 @@ import ch.dissem.bitmessage.utils.TTL
|
||||
import ch.dissem.bitmessage.utils.TestUtils
|
||||
import ch.dissem.bitmessage.utils.UnixTime.MINUTE
|
||||
import com.nhaarman.mockito_kotlin.*
|
||||
import org.hamcrest.CoreMatchers.`is`
|
||||
import org.hamcrest.CoreMatchers.notNullValue
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Assertions.*
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.util.*
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
@ -47,15 +45,16 @@ import kotlin.concurrent.thread
|
||||
* @author Christian Basler
|
||||
*/
|
||||
class BitmessageContextTest {
|
||||
private var listener: BitmessageContext.Listener = mock()
|
||||
private val inventory = spy(TestInventory())
|
||||
private var testListener: BitmessageContext.Listener = mock()
|
||||
private val testInventory = spy(TestInventory())
|
||||
private val testPowRepo = spy(object : ProofOfWorkRepository {
|
||||
internal var items: MutableMap<InventoryVector, ProofOfWorkRepository.Item> = HashMap()
|
||||
internal var added = 0
|
||||
internal var removed = 0
|
||||
|
||||
override fun getItem(initialHash: ByteArray): ProofOfWorkRepository.Item {
|
||||
return items[InventoryVector(initialHash)] ?: throw IllegalArgumentException("${hex(initialHash)} not found in $items")
|
||||
return items[InventoryVector(initialHash)]
|
||||
?: throw IllegalArgumentException("${hex(initialHash)} not found in $items")
|
||||
}
|
||||
|
||||
override fun getItems(): List<ByteArray> {
|
||||
@ -72,7 +71,10 @@ class BitmessageContextTest {
|
||||
}
|
||||
|
||||
override fun putObject(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) {
|
||||
items.put(InventoryVector(cryptography().getInitialHash(objectMessage)), ProofOfWorkRepository.Item(objectMessage, nonceTrialsPerByte, extraBytes))
|
||||
items.put(
|
||||
InventoryVector(cryptography().getInitialHash(objectMessage)),
|
||||
ProofOfWorkRepository.Item(objectMessage, nonceTrialsPerByte, extraBytes)
|
||||
)
|
||||
added++
|
||||
}
|
||||
|
||||
@ -93,26 +95,27 @@ class BitmessageContextTest {
|
||||
thread { callback.onNonceCalculated(initialHash, ByteArray(8)) }
|
||||
}
|
||||
})
|
||||
private var ctx = BitmessageContext.Builder()
|
||||
.addressRepo(mock())
|
||||
.cryptography(BouncyCryptography())
|
||||
.inventory(inventory)
|
||||
.listener(listener)
|
||||
.messageRepo(mock())
|
||||
.networkHandler(mock {
|
||||
private var ctx = BitmessageContext.build {
|
||||
addressRepo = mock()
|
||||
cryptography = BouncyCryptography()
|
||||
inventory = testInventory
|
||||
listener = testListener
|
||||
labelRepo = mock()
|
||||
messageRepo = mock()
|
||||
networkHandler = mock {
|
||||
on { getNetworkStatus() } doReturn Property("test", "mocked")
|
||||
})
|
||||
.nodeRegistry(mock())
|
||||
.labeler(spy(DefaultLabeler()))
|
||||
.powRepo(testPowRepo)
|
||||
.proofOfWorkEngine(testPowEngine)
|
||||
.build()
|
||||
}
|
||||
nodeRegistry = mock()
|
||||
labeler = spy(DefaultLabeler())
|
||||
proofOfWorkRepo = testPowRepo
|
||||
proofOfWorkEngine = testPowEngine
|
||||
}
|
||||
|
||||
init {
|
||||
TTL.msg = 2 * MINUTE
|
||||
}
|
||||
|
||||
@Before
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
testPowRepo.reset()
|
||||
}
|
||||
@ -125,7 +128,7 @@ class BitmessageContextTest {
|
||||
ctx.addContact(contact)
|
||||
|
||||
verify(ctx.addresses, timeout(1000).atLeastOnce()).save(eq(contact))
|
||||
verify(testPowEngine, timeout(1000)).calculateNonce(any(), any(), any())
|
||||
verify(testPowEngine, timeout(1000)).calculateNonce(any(), any(), any<ProofOfWorkEngine.Callback>())
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -138,12 +141,12 @@ class BitmessageContextTest {
|
||||
ctx.addContact(contact)
|
||||
|
||||
verify(ctx.addresses, times(1)).save(contact)
|
||||
verify(testPowEngine, never()).calculateNonce(any(), any(), any())
|
||||
verify(testPowEngine, never()).calculateNonce(any(), any(), any<ProofOfWorkEngine.Callback>())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure V2Pubkey is not requested if it exists in inventory`() {
|
||||
inventory.init(
|
||||
testInventory.init(
|
||||
"V1Msg.payload",
|
||||
"V2GetPubkey.payload",
|
||||
"V2Pubkey.payload",
|
||||
@ -161,12 +164,12 @@ class BitmessageContextTest {
|
||||
ctx.addContact(contact)
|
||||
|
||||
verify(ctx.addresses, atLeastOnce()).save(contact)
|
||||
verify(testPowEngine, never()).calculateNonce(any(), any(), any())
|
||||
verify(testPowEngine, never()).calculateNonce(any(), any(), any<ProofOfWorkEngine.Callback>())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure V4Pubkey is not requested if it exists in inventory`() {
|
||||
inventory.init(
|
||||
testInventory.init(
|
||||
"V1Msg.payload",
|
||||
"V2GetPubkey.payload",
|
||||
"V2Pubkey.payload",
|
||||
@ -185,14 +188,14 @@ class BitmessageContextTest {
|
||||
ctx.addContact(contact)
|
||||
|
||||
verify(ctx.addresses, atLeastOnce()).save(any())
|
||||
verify(testPowEngine, never()).calculateNonce(any(), any(), any())
|
||||
verify(testPowEngine, never()).calculateNonce(any(), any(), any<ProofOfWorkEngine.Callback>())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure subscription is added and existing broadcasts retrieved`() {
|
||||
val address = BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ")
|
||||
|
||||
inventory.init(
|
||||
testInventory.init(
|
||||
"V4Broadcast.payload",
|
||||
"V5Broadcast.payload"
|
||||
)
|
||||
@ -201,60 +204,73 @@ class BitmessageContextTest {
|
||||
ctx.addSubscribtion(address)
|
||||
|
||||
verify(ctx.addresses, atLeastOnce()).save(address)
|
||||
assertThat(address.isSubscribed, `is`(true))
|
||||
assertTrue(address.isSubscribed)
|
||||
verify(ctx.internals.inventory).getObjects(eq(address.stream), any(), any())
|
||||
verify(listener).receive(any())
|
||||
verify(testListener).receive(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure identity is created`() {
|
||||
assertThat(ctx.createIdentity(false), notNullValue())
|
||||
assertNotNull(ctx.createIdentity(false))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure message is sent`() {
|
||||
ctx.send(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), TestUtils.loadContact(),
|
||||
"Subject", "Message")
|
||||
ctx.send(
|
||||
TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), TestUtils.loadContact(),
|
||||
"Subject", "Message"
|
||||
)
|
||||
verify(ctx.internals.proofOfWorkRepository, timeout(10000)).putObject(
|
||||
argThat { payload.type == ObjectType.MSG }, eq(1000L), eq(1000L))
|
||||
argThat { payload.type == ObjectType.MSG }, eq(1000L), eq(1000L)
|
||||
)
|
||||
assertEquals(2, testPowRepo.added)
|
||||
verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat<Plaintext> { type == Type.MSG })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure pubkey is requested if it is missing`() {
|
||||
ctx.send(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"),
|
||||
ctx.send(
|
||||
TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"),
|
||||
BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"),
|
||||
"Subject", "Message")
|
||||
"Subject", "Message"
|
||||
)
|
||||
verify(testPowRepo, timeout(10000).atLeastOnce())
|
||||
.putObject(argThat { payload.type == ObjectType.GET_PUBKEY }, eq(1000L), eq(1000L))
|
||||
verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat<Plaintext> { type == Type.MSG })
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException::class)
|
||||
@Test
|
||||
fun `ensure sender must be identity`() {
|
||||
ctx.send(BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"),
|
||||
BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"),
|
||||
"Subject", "Message")
|
||||
assertThrows(IllegalArgumentException::class.java) {
|
||||
ctx.send(
|
||||
BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"),
|
||||
BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"),
|
||||
"Subject", "Message"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure broadcast is sent`() {
|
||||
ctx.broadcast(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"),
|
||||
"Subject", "Message")
|
||||
ctx.broadcast(
|
||||
TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"),
|
||||
"Subject", "Message"
|
||||
)
|
||||
verify(ctx.internals.proofOfWorkRepository, timeout(1000).atLeastOnce())
|
||||
.putObject(argThat { payload.type == ObjectType.BROADCAST }, eq(1000L), eq(1000L))
|
||||
verify(testPowEngine).calculateNonce(any(), any(), any())
|
||||
verify(testPowEngine).calculateNonce(any(), any(), any<ProofOfWorkEngine.Callback>())
|
||||
verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat<Plaintext> { type == Type.BROADCAST })
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException::class)
|
||||
@Test
|
||||
fun `ensure sender without private key throws exception`() {
|
||||
val msg = Plaintext.Builder(Type.BROADCAST)
|
||||
.from(BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
|
||||
.message("Subject", "Message")
|
||||
.build()
|
||||
ctx.send(msg)
|
||||
assertThrows(IllegalArgumentException::class.java) {
|
||||
val msg = Plaintext.Builder(Type.BROADCAST)
|
||||
.from(BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
|
||||
.message("Subject", "Message")
|
||||
.build()
|
||||
ctx.send(msg)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -325,6 +341,6 @@ class BitmessageContextTest {
|
||||
@Test
|
||||
fun `ensure status contains user agent`() {
|
||||
val userAgent = ctx.status().getProperty("user agent")?.value.toString()
|
||||
assertThat(userAgent, `is`("/Jabit:${BitmessageContext.version}/"))
|
||||
assertEquals("/Jabit:${BitmessageContext.version}/", userAgent)
|
||||
}
|
||||
}
|
||||
|
@ -21,9 +21,9 @@ import ch.dissem.bitmessage.entity.payload.V4Broadcast
|
||||
import ch.dissem.bitmessage.entity.payload.V5Broadcast
|
||||
import ch.dissem.bitmessage.utils.TestBase
|
||||
import ch.dissem.bitmessage.utils.TestUtils
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class DecryptionTest : TestBase() {
|
||||
@Test
|
||||
|
@ -34,8 +34,8 @@ import ch.dissem.bitmessage.utils.TestUtils
|
||||
import ch.dissem.bitmessage.utils.UnixTime.MINUTE
|
||||
import ch.dissem.bitmessage.utils.UnixTime.now
|
||||
import com.nhaarman.mockito_kotlin.*
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
/**
|
||||
* @author Christian Basler
|
||||
@ -47,7 +47,7 @@ class DefaultMessageListenerTest : TestBase() {
|
||||
cryptography = BouncyCryptography()
|
||||
)
|
||||
|
||||
@Before
|
||||
@BeforeAll
|
||||
fun setUp() {
|
||||
listener = ctx.networkListener as DefaultMessageListener
|
||||
}
|
||||
|
@ -24,9 +24,9 @@ import ch.dissem.bitmessage.entity.valueobject.PrivateKey
|
||||
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||
import ch.dissem.bitmessage.utils.TestBase
|
||||
import ch.dissem.bitmessage.utils.TestUtils
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNotNull
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class EncryptionTest : TestBase() {
|
||||
@Test
|
||||
|
@ -23,14 +23,14 @@ import ch.dissem.bitmessage.entity.Plaintext.Type.MSG
|
||||
import ch.dissem.bitmessage.entity.payload.GenericPayload
|
||||
import ch.dissem.bitmessage.entity.payload.Msg
|
||||
import ch.dissem.bitmessage.ports.Cryptography
|
||||
import ch.dissem.bitmessage.ports.ProofOfWorkEngine
|
||||
import ch.dissem.bitmessage.ports.ProofOfWorkRepository
|
||||
import ch.dissem.bitmessage.utils.Singleton
|
||||
import ch.dissem.bitmessage.utils.TestUtils
|
||||
import com.nhaarman.mockito_kotlin.*
|
||||
import org.hamcrest.CoreMatchers.equalTo
|
||||
import org.junit.Assert.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.util.*
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
@ -43,7 +43,7 @@ class ProofOfWorkServiceTest {
|
||||
|
||||
private var obj by Delegates.notNull<ObjectMessage>()
|
||||
|
||||
@Before
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
cryptography = spy(BouncyCryptography())
|
||||
Singleton.initialize(cryptography)
|
||||
@ -65,11 +65,11 @@ class ProofOfWorkServiceTest {
|
||||
fun `ensure missing proof of work is done`() {
|
||||
whenever(ctx.proofOfWorkRepository.getItems()).thenReturn(Arrays.asList<ByteArray>(ByteArray(64)))
|
||||
whenever(ctx.proofOfWorkRepository.getItem(any())).thenReturn(ProofOfWorkRepository.Item(obj, 1001, 1002))
|
||||
doNothing().whenever(cryptography).doProofOfWork(any(), any(), any(), any())
|
||||
doNothing().whenever(cryptography).doProofOfWork(any(), any(), any(), any<ProofOfWorkEngine.Callback>())
|
||||
|
||||
ctx.proofOfWorkService.doMissingProofOfWork(10)
|
||||
|
||||
verify(cryptography, timeout(1000)).doProofOfWork(eq(obj), eq(1001L), eq(1002L), any())
|
||||
verify(cryptography, timeout(1000)).doProofOfWork(eq(obj), eq(1001L), eq(1002L), any<ProofOfWorkEngine.Callback>())
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -95,6 +95,6 @@ class ProofOfWorkServiceTest {
|
||||
verify(ctx.proofOfWorkRepository).removeObject(eq(initialHash))
|
||||
verify(ctx.inventory).storeObject(eq(objectMessage))
|
||||
verify(ctx.networkHandler).offer(eq(objectMessage.inventoryVector))
|
||||
assertThat(plaintext.inventoryVector, equalTo(objectMessage.inventoryVector))
|
||||
assertEquals(objectMessage.inventoryVector, plaintext.inventoryVector)
|
||||
}
|
||||
}
|
||||
|
@ -23,8 +23,8 @@ import ch.dissem.bitmessage.entity.payload.Pubkey
|
||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
|
||||
import ch.dissem.bitmessage.utils.TestBase
|
||||
import ch.dissem.bitmessage.utils.TestUtils
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Assertions.*
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class SignatureTest : TestBase() {
|
||||
@Test
|
||||
|
@ -22,16 +22,15 @@ import ch.dissem.bitmessage.entity.payload.Pubkey.Feature.INCLUDE_DESTINATION
|
||||
import ch.dissem.bitmessage.entity.payload.V4Pubkey
|
||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
|
||||
import ch.dissem.bitmessage.utils.*
|
||||
import org.junit.Assert
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Assertions.*
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
||||
class BitmessageAddressTest : TestBase() {
|
||||
@Test
|
||||
fun `ensure feature flag is calculated correctly`() {
|
||||
Assert.assertEquals(1, Pubkey.Feature.bitfield(DOES_ACK))
|
||||
assertEquals(1, Pubkey.Feature.bitfield(DOES_ACK))
|
||||
assertEquals(2, Pubkey.Feature.bitfield(INCLUDE_DESTINATION))
|
||||
assertEquals(3, Pubkey.Feature.bitfield(DOES_ACK, INCLUDE_DESTINATION))
|
||||
}
|
||||
@ -74,7 +73,7 @@ class BitmessageAddressTest : TestBase() {
|
||||
try {
|
||||
address.pubkey = pubkey
|
||||
} catch (e: Exception) {
|
||||
fail(e.message)
|
||||
fail<Unit>(e.message)
|
||||
}
|
||||
|
||||
}
|
||||
@ -82,7 +81,7 @@ class BitmessageAddressTest : TestBase() {
|
||||
@Test
|
||||
fun `ensure V3Pubkey can be imported`() {
|
||||
val address = BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ")
|
||||
Assert.assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), address.ripe)
|
||||
assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), address.ripe)
|
||||
|
||||
val objectMessage = TestUtils.loadObjectMessage(3, "V3Pubkey.payload")
|
||||
val pubkey = objectMessage.payload as Pubkey
|
||||
@ -90,7 +89,7 @@ class BitmessageAddressTest : TestBase() {
|
||||
try {
|
||||
address.pubkey = pubkey
|
||||
} catch (e: Exception) {
|
||||
fail(e.message)
|
||||
fail<Unit>(e.message)
|
||||
}
|
||||
|
||||
assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), pubkey.ripe)
|
||||
@ -107,7 +106,7 @@ class BitmessageAddressTest : TestBase() {
|
||||
try {
|
||||
address.pubkey = pubkey
|
||||
} catch (e: Exception) {
|
||||
fail(e.message)
|
||||
fail<Unit>(e.message)
|
||||
}
|
||||
|
||||
assertTrue(address.has(DOES_ACK))
|
||||
|
File diff suppressed because one or more lines are too long
@ -24,9 +24,8 @@ import ch.dissem.bitmessage.entity.valueobject.extended.Message
|
||||
import ch.dissem.bitmessage.factory.Factory
|
||||
import ch.dissem.bitmessage.utils.TestBase
|
||||
import ch.dissem.bitmessage.utils.TestUtils
|
||||
import org.hamcrest.Matchers.`is`
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Assertions.*
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.ObjectInputStream
|
||||
@ -86,16 +85,16 @@ class SerializationTest : TestBase() {
|
||||
.signature(ByteArray(0))
|
||||
.build()
|
||||
val out = ByteArrayOutputStream()
|
||||
expected.write(out)
|
||||
val `in` = ByteArrayInputStream(out.toByteArray())
|
||||
val actual = Plaintext.read(MSG, `in`)
|
||||
expected.writer().write(out)
|
||||
val input = ByteArrayInputStream(out.toByteArray())
|
||||
val actual = Plaintext.read(MSG, input)
|
||||
|
||||
// Received is automatically set on deserialization, so we'll need to set it to null
|
||||
val received = Plaintext::class.java.getDeclaredField("received")
|
||||
received.isAccessible = true
|
||||
received.set(actual, null)
|
||||
|
||||
assertThat(expected, `is`(actual))
|
||||
assertEquals(actual, expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -103,17 +102,46 @@ class SerializationTest : TestBase() {
|
||||
val expected = Plaintext.Builder(MSG)
|
||||
.from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
|
||||
.to(TestUtils.loadContact())
|
||||
.message(Message.Builder()
|
||||
.subject("Subject")
|
||||
.body("Message")
|
||||
.build())
|
||||
.message(
|
||||
Message.Builder()
|
||||
.subject("Subject")
|
||||
.body("Message")
|
||||
.build()
|
||||
)
|
||||
.ackData("ackMessage".toByteArray())
|
||||
.signature(ByteArray(0))
|
||||
.build()
|
||||
val out = ByteArrayOutputStream()
|
||||
expected.write(out)
|
||||
val `in` = ByteArrayInputStream(out.toByteArray())
|
||||
val actual = Plaintext.read(MSG, `in`)
|
||||
expected.writer().write(out)
|
||||
val input = ByteArrayInputStream(out.toByteArray())
|
||||
val actual = Plaintext.read(MSG, input)
|
||||
|
||||
// Received is automatically set on deserialization, so we'll need to set it to null
|
||||
val received = Plaintext::class.java.getDeclaredField("received")
|
||||
received.isAccessible = true
|
||||
received.set(actual, null)
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure plaintext without recipient can be serialized (needed for saving drafts)`() {
|
||||
val expected = Plaintext.Builder(MSG)
|
||||
.from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
|
||||
.message(
|
||||
Message.Builder()
|
||||
.subject("Subject")
|
||||
.body("Message")
|
||||
.build()
|
||||
)
|
||||
.signature(ByteArray(0))
|
||||
.status(Plaintext.Status.DRAFT)
|
||||
.build()
|
||||
val out = ByteArrayOutputStream()
|
||||
expected.writer().write(out)
|
||||
val input = ByteArrayInputStream(out.toByteArray())
|
||||
val actual = Plaintext.read(MSG, input)
|
||||
actual.status = Plaintext.Status.DRAFT // status isn't serialized, that's OK
|
||||
|
||||
// Received is automatically set on deserialization, so we'll need to set it to null
|
||||
val received = Plaintext::class.java.getDeclaredField("received")
|
||||
@ -136,9 +164,9 @@ class SerializationTest : TestBase() {
|
||||
assertNotNull(ackMessage1)
|
||||
|
||||
val out = ByteArrayOutputStream()
|
||||
expected.write(out)
|
||||
val `in` = ByteArrayInputStream(out.toByteArray())
|
||||
val actual = Plaintext.read(MSG, `in`)
|
||||
expected.writer().write(out)
|
||||
val input = ByteArrayInputStream(out.toByteArray())
|
||||
val actual = Plaintext.read(MSG, input)
|
||||
|
||||
// Received is automatically set on deserialization, so we'll need to set it to null
|
||||
val received = Plaintext::class.java.getDeclaredField("received")
|
||||
@ -159,7 +187,7 @@ class SerializationTest : TestBase() {
|
||||
val inv = Inv(ivs)
|
||||
val before = NetworkMessage(inv)
|
||||
val out = ByteArrayOutputStream()
|
||||
before.write(out)
|
||||
before.writer().write(out)
|
||||
|
||||
val after = Factory.getNetworkMessage(3, ByteArrayInputStream(out.toByteArray()))
|
||||
assertNotNull(after)
|
||||
@ -169,11 +197,11 @@ class SerializationTest : TestBase() {
|
||||
|
||||
private fun doTest(resourceName: String, version: Int, expectedPayloadType: Class<*>) {
|
||||
val data = TestUtils.getBytes(resourceName)
|
||||
val `in` = ByteArrayInputStream(data)
|
||||
val objectMessage = Factory.getObjectMessage(version, `in`, data.size)
|
||||
val input = ByteArrayInputStream(data)
|
||||
val objectMessage = Factory.getObjectMessage(version, input, data.size)
|
||||
val out = ByteArrayOutputStream()
|
||||
assertNotNull(objectMessage)
|
||||
objectMessage!!.write(out)
|
||||
objectMessage!!.writer().write(out)
|
||||
assertArrayEquals(data, out.toByteArray())
|
||||
assertEquals(expectedPayloadType.canonicalName, objectMessage.payload.javaClass.canonicalName)
|
||||
}
|
||||
@ -190,8 +218,8 @@ class SerializationTest : TestBase() {
|
||||
val oos = ObjectOutputStream(out)
|
||||
oos.writeObject(plaintext)
|
||||
|
||||
val `in` = ByteArrayInputStream(out.toByteArray())
|
||||
val ois = ObjectInputStream(`in`)
|
||||
val input = ByteArrayInputStream(out.toByteArray())
|
||||
val ois = ObjectInputStream(input)
|
||||
assertEquals(plaintext, ois.readObject())
|
||||
}
|
||||
}
|
||||
|
@ -20,18 +20,24 @@ import ch.dissem.bitmessage.utils.Bytes
|
||||
import ch.dissem.bitmessage.utils.CallbackWaiter
|
||||
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||
import ch.dissem.bitmessage.utils.TestBase
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Assertions.assertTimeoutPreemptively
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.time.Duration.ofSeconds
|
||||
|
||||
class ProofOfWorkEngineTest : TestBase() {
|
||||
@Test(timeout = 90000)
|
||||
@Test
|
||||
fun `test SimplePOWEngine`() {
|
||||
testPOW(SimplePOWEngine())
|
||||
assertTimeoutPreemptively(ofSeconds(90)) {
|
||||
testPOW(SimplePOWEngine())
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 90000)
|
||||
@Test
|
||||
fun `test MultiThreadedPOWEngine`() {
|
||||
testPOW(MultiThreadedPOWEngine())
|
||||
assertTimeoutPreemptively(ofSeconds(90)) {
|
||||
testPOW(MultiThreadedPOWEngine())
|
||||
}
|
||||
}
|
||||
|
||||
private fun testPOW(engine: ProofOfWorkEngine) {
|
||||
@ -65,6 +71,6 @@ class ProofOfWorkEngineTest : TestBase() {
|
||||
val nonce2 = waiter2.waitForValue()!!
|
||||
println("Calculating nonce1 took ${waiter2.time}ms")
|
||||
assertTrue(Bytes.lt(cryptography().doubleSha512(nonce2, initialHash2), target2, 8))
|
||||
assertTrue("Second nonce1 must be quicker to find", waiter1.time > waiter2.time)
|
||||
assertTrue(waiter1.time > waiter2.time, "Second nonce1 must be quicker to find")
|
||||
}
|
||||
}
|
||||
|
@ -17,9 +17,8 @@
|
||||
package ch.dissem.bitmessage.utils
|
||||
|
||||
import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography
|
||||
import org.hamcrest.Matchers.`is`
|
||||
import org.junit.Assert.assertThat
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class Base64Test {
|
||||
@Test
|
||||
@ -29,7 +28,7 @@ class Base64Test {
|
||||
val data = cryptography.randomBytes(i)
|
||||
val string = Base64.encodeToString(data)
|
||||
val decoded = Base64.decode(string)
|
||||
assertThat(decoded, `is`(data))
|
||||
assertEquals(data, decoded)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,10 +16,10 @@
|
||||
|
||||
package ch.dissem.bitmessage.utils
|
||||
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Assertions.assertArrayEquals
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Disabled
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.math.BigInteger
|
||||
import java.util.*
|
||||
|
||||
@ -46,7 +46,11 @@ class BytesTest {
|
||||
for (i in 1..255) {
|
||||
val bytes = byteArrayOf(0, v.toByte())
|
||||
Bytes.inc(bytes, i.toByte())
|
||||
assertArrayEquals("value = " + v + "; inc = " + i + "; expected = " + (v + i), TestUtils.int16(v + i), bytes)
|
||||
assertArrayEquals(
|
||||
TestUtils.int16(v + i),
|
||||
bytes,
|
||||
"value = " + v + "; inc = " + i + "; expected = " + (v + i)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -55,7 +59,7 @@ class BytesTest {
|
||||
* This test is used to compare different implementations of the single byte lt comparison. It an safely be ignored.
|
||||
*/
|
||||
@Test
|
||||
@Ignore
|
||||
@Disabled
|
||||
fun `test lower than single byte`() {
|
||||
val a = ByteArray(1)
|
||||
val b = ByteArray(1)
|
||||
@ -85,10 +89,13 @@ class BytesTest {
|
||||
val a = BigInteger.valueOf(rnd.nextLong()).pow(rnd.nextInt(5) + 1).abs()
|
||||
val b = BigInteger.valueOf(rnd.nextLong()).pow(rnd.nextInt(5) + 1).abs()
|
||||
println("a = " + a.toString(16) + "\tb = " + b.toString(16))
|
||||
assertEquals(a.compareTo(b) == -1, Bytes.lt(
|
||||
Bytes.expand(a.toByteArray(), 100),
|
||||
Bytes.expand(b.toByteArray(), 100),
|
||||
100))
|
||||
assertEquals(
|
||||
a.compareTo(b) == -1, Bytes.lt(
|
||||
Bytes.expand(a.toByteArray(), 100),
|
||||
Bytes.expand(b.toByteArray(), 100),
|
||||
100
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,17 +16,12 @@
|
||||
|
||||
package ch.dissem.bitmessage.utils
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import java.util.LinkedList
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class CollectionsTest {
|
||||
@Test
|
||||
fun `ensure select random returns maximum possible items`() {
|
||||
val list = LinkedList<Int>()
|
||||
list += 0..9
|
||||
assertEquals(9, Collections.selectRandom(9, list).size)
|
||||
assertEquals(9, Collections.selectRandom(9, listOf(0..9)).size)
|
||||
}
|
||||
}
|
||||
|
@ -24,9 +24,8 @@ import ch.dissem.bitmessage.entity.valueobject.extended.Message
|
||||
import ch.dissem.bitmessage.ports.MessageRepository
|
||||
import ch.dissem.bitmessage.utils.TestUtils.RANDOM
|
||||
import com.nhaarman.mockito_kotlin.*
|
||||
import org.hamcrest.Matchers.`is`
|
||||
import org.junit.Assert.assertThat
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.util.*
|
||||
|
||||
class ConversationServiceTest : TestBase() {
|
||||
@ -45,7 +44,7 @@ class ConversationServiceTest : TestBase() {
|
||||
|
||||
doReturn(expected).whenever(conversationService).getConversation(any<UUID>())
|
||||
val actual = conversationService.getConversation(UUID.randomUUID())
|
||||
assertThat(actual, `is`(expected))
|
||||
Assertions.assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@ -53,57 +52,69 @@ class ConversationServiceTest : TestBase() {
|
||||
fun conversation(alice: BitmessageAddress, bob: BitmessageAddress): List<Plaintext> {
|
||||
val result = LinkedList<Plaintext>()
|
||||
|
||||
val older = plaintext(alice, bob,
|
||||
val older = plaintext(
|
||||
alice, bob,
|
||||
Message.Builder()
|
||||
.subject("hey there")
|
||||
.body("does it work?")
|
||||
.build(),
|
||||
Plaintext.Status.SENT)
|
||||
Plaintext.Status.SENT
|
||||
)
|
||||
result.add(older)
|
||||
|
||||
val root = plaintext(alice, bob,
|
||||
val root = plaintext(
|
||||
alice, bob,
|
||||
Message.Builder()
|
||||
.subject("new test")
|
||||
.body("There's a new test in town!")
|
||||
.build(),
|
||||
Plaintext.Status.SENT)
|
||||
Plaintext.Status.SENT
|
||||
)
|
||||
result.add(root)
|
||||
|
||||
result.add(
|
||||
plaintext(bob, alice,
|
||||
plaintext(
|
||||
bob, alice,
|
||||
Message.Builder()
|
||||
.subject("Re: new test (1a)")
|
||||
.body("Nice!")
|
||||
.addParent(root)
|
||||
.build(),
|
||||
Plaintext.Status.RECEIVED)
|
||||
Plaintext.Status.RECEIVED
|
||||
)
|
||||
)
|
||||
|
||||
val latest = plaintext(bob, alice,
|
||||
val latest = plaintext(
|
||||
bob, alice,
|
||||
Message.Builder()
|
||||
.subject("Re: new test (2b)")
|
||||
.body("PS: it did work!")
|
||||
.addParent(root)
|
||||
.addParent(older)
|
||||
.build(),
|
||||
Plaintext.Status.RECEIVED)
|
||||
Plaintext.Status.RECEIVED
|
||||
)
|
||||
result.add(latest)
|
||||
|
||||
result.add(
|
||||
plaintext(alice, bob,
|
||||
plaintext(
|
||||
alice, bob,
|
||||
Message.Builder()
|
||||
.subject("Re: new test (2)")
|
||||
.body("")
|
||||
.addParent(latest)
|
||||
.build(),
|
||||
Plaintext.Status.DRAFT)
|
||||
Plaintext.Status.DRAFT
|
||||
)
|
||||
)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
fun plaintext(from: BitmessageAddress, to: BitmessageAddress,
|
||||
content: ExtendedEncoding, status: Plaintext.Status): Plaintext {
|
||||
fun plaintext(
|
||||
from: BitmessageAddress, to: BitmessageAddress,
|
||||
content: ExtendedEncoding, status: Plaintext.Status
|
||||
): Plaintext {
|
||||
val builder = Plaintext.Builder(MSG)
|
||||
.IV(TestUtils.randomInventoryVector())
|
||||
.from(from)
|
||||
|
@ -16,8 +16,8 @@
|
||||
|
||||
package ch.dissem.bitmessage.utils
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
@ -34,8 +34,8 @@ class DecodeTest {
|
||||
}
|
||||
|
||||
private fun testCodec(number: Long) {
|
||||
val `is` = ByteArrayOutputStream()
|
||||
Encode.varInt(number, `is`)
|
||||
assertEquals(number, Decode.varInt(ByteArrayInputStream(`is`.toByteArray())))
|
||||
val out = ByteArrayOutputStream()
|
||||
Encode.varInt(number, out)
|
||||
assertEquals(number, Decode.varInt(ByteArrayInputStream(out.toByteArray())))
|
||||
}
|
||||
}
|
||||
|
@ -16,8 +16,9 @@
|
||||
|
||||
package ch.dissem.bitmessage.utils
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assumptions
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
class EncodeTest {
|
||||
@ -111,7 +112,8 @@ class EncodeTest {
|
||||
|
||||
|
||||
fun checkBytes(stream: ByteArrayOutputStream, vararg bytes: Int) {
|
||||
assertEquals(bytes.size, stream.size())
|
||||
Assumptions.assumeTrue(bytes.size == stream.size())
|
||||
|
||||
val streamBytes = stream.toByteArray()
|
||||
|
||||
for (i in bytes.indices) {
|
||||
|
@ -16,14 +16,12 @@
|
||||
|
||||
package ch.dissem.bitmessage.utils
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class SqlStringsTest {
|
||||
@Test
|
||||
fun `ensure join works with long array`() {
|
||||
val test = longArrayOf(1L, 2L)
|
||||
assertEquals("1, 2", SqlStrings.join(*test))
|
||||
assertEquals("1, 2", SqlStrings.join(1L, 2L))
|
||||
}
|
||||
}
|
||||
|
@ -16,9 +16,8 @@
|
||||
|
||||
package ch.dissem.bitmessage.utils
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class StringsTest {
|
||||
@Test
|
||||
|
@ -17,15 +17,16 @@
|
||||
package ch.dissem.bitmessage.utils
|
||||
|
||||
import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
|
||||
/**
|
||||
* @author Christian Basler
|
||||
*/
|
||||
open class TestBase {
|
||||
companion object {
|
||||
@BeforeClass
|
||||
@JvmStatic fun setUpClass() {
|
||||
@BeforeAll
|
||||
@JvmStatic
|
||||
fun setUpClass() {
|
||||
Singleton.initialize(BouncyCryptography())
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ package ch.dissem.bitmessage.utils
|
||||
|
||||
import ch.dissem.bitmessage.BitmessageContext
|
||||
import ch.dissem.bitmessage.InternalContext
|
||||
import ch.dissem.bitmessage.Preferences
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage
|
||||
import ch.dissem.bitmessage.entity.payload.V4Pubkey
|
||||
@ -28,7 +29,7 @@ import ch.dissem.bitmessage.factory.Factory
|
||||
import ch.dissem.bitmessage.ports.*
|
||||
import com.nhaarman.mockito_kotlin.mock
|
||||
import com.nhaarman.mockito_kotlin.spy
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.InputStream
|
||||
@ -39,43 +40,50 @@ import kotlin.NoSuchElementException
|
||||
* If there's ever a need for this in production code, it should be rewritten to be more efficient.
|
||||
*/
|
||||
object TestUtils {
|
||||
@JvmField val RANDOM = Random()
|
||||
@JvmField
|
||||
val RANDOM = Random()
|
||||
|
||||
@JvmStatic fun int16(number: Int): ByteArray {
|
||||
@JvmStatic
|
||||
fun int16(number: Int): ByteArray {
|
||||
val out = ByteArrayOutputStream()
|
||||
Encode.int16(number, out)
|
||||
return out.toByteArray()
|
||||
}
|
||||
|
||||
@JvmStatic fun loadObjectMessage(version: Int, resourceName: String): ObjectMessage {
|
||||
@JvmStatic
|
||||
fun loadObjectMessage(version: Int, resourceName: String): ObjectMessage {
|
||||
val data = getBytes(resourceName)
|
||||
val `in` = ByteArrayInputStream(data)
|
||||
return Factory.getObjectMessage(version, `in`, data.size) ?: throw NoSuchElementException("error loading object message")
|
||||
val input = ByteArrayInputStream(data)
|
||||
return Factory.getObjectMessage(version, input, data.size)
|
||||
?: throw NoSuchElementException("error loading object message")
|
||||
}
|
||||
|
||||
@JvmStatic fun getBytes(resourceName: String): ByteArray {
|
||||
val `in` = javaClass.classLoader.getResourceAsStream(resourceName)
|
||||
@JvmStatic
|
||||
fun getBytes(resourceName: String): ByteArray {
|
||||
val input = javaClass.classLoader.getResourceAsStream(resourceName)
|
||||
val out = ByteArrayOutputStream()
|
||||
val buffer = ByteArray(1024)
|
||||
var len = `in`.read(buffer)
|
||||
var len = input.read(buffer)
|
||||
while (len != -1) {
|
||||
out.write(buffer, 0, len)
|
||||
len = `in`.read(buffer)
|
||||
len = input.read(buffer)
|
||||
}
|
||||
return out.toByteArray()
|
||||
}
|
||||
|
||||
@JvmStatic fun randomInventoryVector(): InventoryVector {
|
||||
@JvmStatic
|
||||
fun randomInventoryVector(): InventoryVector {
|
||||
val bytes = ByteArray(32)
|
||||
RANDOM.nextBytes(bytes)
|
||||
return InventoryVector(bytes)
|
||||
}
|
||||
|
||||
@JvmStatic fun getResource(resourceName: String): InputStream {
|
||||
return javaClass.classLoader.getResourceAsStream(resourceName)
|
||||
}
|
||||
@JvmStatic
|
||||
fun getResource(resourceName: String): InputStream =
|
||||
javaClass.classLoader.getResourceAsStream(resourceName)
|
||||
|
||||
@JvmStatic fun loadIdentity(address: String): BitmessageAddress {
|
||||
@JvmStatic
|
||||
fun loadIdentity(address: String): BitmessageAddress {
|
||||
val privateKey = PrivateKey.read(TestUtils.getResource(address + ".privkey"))
|
||||
val identity = BitmessageAddress(privateKey)
|
||||
assertEquals(address, identity.address)
|
||||
@ -83,7 +91,8 @@ object TestUtils {
|
||||
}
|
||||
|
||||
@Throws(DecryptionFailedException::class)
|
||||
@JvmStatic fun loadContact(): BitmessageAddress {
|
||||
@JvmStatic
|
||||
fun loadContact(): BitmessageAddress {
|
||||
val address = BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h")
|
||||
val objectMessage = TestUtils.loadObjectMessage(3, "V4Pubkey.payload")
|
||||
objectMessage.decrypt(address.publicDecryptionKey)
|
||||
@ -91,18 +100,21 @@ object TestUtils {
|
||||
return address
|
||||
}
|
||||
|
||||
@JvmStatic fun loadPubkey(address: BitmessageAddress) {
|
||||
@JvmStatic
|
||||
fun loadPubkey(address: BitmessageAddress) {
|
||||
val bytes = getBytes(address.address + ".pubkey")
|
||||
val pubkey = Factory.readPubkey(address.version, address.stream, ByteArrayInputStream(bytes), bytes.size, false)
|
||||
address.pubkey = pubkey
|
||||
}
|
||||
|
||||
@JvmStatic fun mockedInternalContext(
|
||||
@JvmStatic
|
||||
fun mockedInternalContext(
|
||||
cryptography: Cryptography = mock {},
|
||||
inventory: Inventory = mock {},
|
||||
nodeRegistry: NodeRegistry = mock {},
|
||||
networkHandler: NetworkHandler = mock {},
|
||||
addressRepository: AddressRepository = mock {},
|
||||
labelRepository: LabelRepository = mock {},
|
||||
messageRepository: MessageRepository = mock {},
|
||||
proofOfWorkRepository: ProofOfWorkRepository = mock {},
|
||||
proofOfWorkEngine: ProofOfWorkEngine = mock {},
|
||||
@ -119,16 +131,19 @@ object TestUtils {
|
||||
nodeRegistry,
|
||||
networkHandler,
|
||||
addressRepository,
|
||||
labelRepository,
|
||||
messageRepository,
|
||||
proofOfWorkRepository,
|
||||
proofOfWorkEngine,
|
||||
customCommandHandler,
|
||||
listener,
|
||||
labeler,
|
||||
"/Jabit:TEST/",
|
||||
port,
|
||||
connectionTTL,
|
||||
connectionLimit
|
||||
Preferences().apply {
|
||||
this.userAgent = "/Jabit:TEST/"
|
||||
this.port = port
|
||||
this.connectionTTL = connectionTTL
|
||||
this.connectionLimit = connectionLimit
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,8 @@ uploadArchives {
|
||||
dependencies {
|
||||
compile project(':core')
|
||||
compile 'org.bouncycastle:bcprov-jdk15on'
|
||||
testCompile 'junit:junit'
|
||||
testCompile 'com.nhaarman:mockito-kotlin'
|
||||
testCompile 'org.junit.jupiter:junit-jupiter-api'
|
||||
testRuntime 'org.junit.jupiter:junit-jupiter-engine'
|
||||
testCompile project(path: ':core', configuration: 'testArtifacts')
|
||||
}
|
||||
|
@ -22,14 +22,15 @@ import ch.dissem.bitmessage.entity.payload.GenericPayload
|
||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
|
||||
import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException
|
||||
import ch.dissem.bitmessage.ports.MultiThreadedPOWEngine
|
||||
import ch.dissem.bitmessage.ports.ProofOfWorkEngine
|
||||
import ch.dissem.bitmessage.utils.*
|
||||
import ch.dissem.bitmessage.utils.CallbackWaiter
|
||||
import ch.dissem.bitmessage.utils.Singleton
|
||||
import ch.dissem.bitmessage.utils.TestUtils
|
||||
import ch.dissem.bitmessage.utils.UnixTime
|
||||
import ch.dissem.bitmessage.utils.UnixTime.DAY
|
||||
import ch.dissem.bitmessage.utils.UnixTime.MINUTE
|
||||
import ch.dissem.bitmessage.utils.UnixTime.now
|
||||
import org.hamcrest.CoreMatchers.`is`
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Assertions.*
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.io.ByteArrayInputStream
|
||||
import javax.xml.bind.DatatypeConverter
|
||||
|
||||
@ -63,7 +64,7 @@ class CryptographyTest {
|
||||
assertArrayEquals(crypto.sha512(TEST_SHA512), crypto.doubleSha512(TEST_VALUE))
|
||||
}
|
||||
|
||||
@Test(expected = InsufficientProofOfWorkException::class)
|
||||
@Test
|
||||
fun `ensure exception for insufficient proof of work`() {
|
||||
val objectMessage = ObjectMessage.Builder()
|
||||
.nonce(ByteArray(8))
|
||||
@ -71,7 +72,9 @@ class CryptographyTest {
|
||||
.objectType(0)
|
||||
.payload(GenericPayload.read(0, 1, ByteArrayInputStream(ByteArray(0)), 0))
|
||||
.build()
|
||||
crypto.checkProofOfWork(objectMessage, 1000, 1000)
|
||||
assertThrows(InsufficientProofOfWorkException::class.java) {
|
||||
crypto.checkProofOfWork(objectMessage, 1000, 1000)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -89,17 +92,12 @@ class CryptographyTest {
|
||||
stream = 1
|
||||
)
|
||||
val waiter = CallbackWaiter<ByteArray>()
|
||||
crypto.doProofOfWork(objectMessage, 1000, 1000,
|
||||
object : ProofOfWorkEngine.Callback {
|
||||
override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) {
|
||||
waiter.setValue(nonce)
|
||||
}
|
||||
})
|
||||
crypto.doProofOfWork(objectMessage, 1000, 1000) { _, nonce -> waiter.setValue(nonce) }
|
||||
objectMessage.nonce = waiter.waitForValue()
|
||||
try {
|
||||
crypto.checkProofOfWork(objectMessage, 1000, 1000)
|
||||
} catch (e: InsufficientProofOfWorkException) {
|
||||
fail(e.message)
|
||||
fail<Unit>(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,12 +111,14 @@ class CryptographyTest {
|
||||
assertArrayEquals(data, decrypted)
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException::class)
|
||||
@Test
|
||||
fun `ensure decryption fails with invalid cypher text`() {
|
||||
val data = crypto.randomBytes(128)
|
||||
val key_e = crypto.randomBytes(32)
|
||||
val iv = crypto.randomBytes(16)
|
||||
crypto.crypt(false, data, key_e, iv)
|
||||
assertThrows(IllegalArgumentException::class.java) {
|
||||
crypto.crypt(false, data, key_e, iv)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -137,7 +137,7 @@ class CryptographyTest {
|
||||
val data = crypto.randomBytes(100)
|
||||
val privateKey = PrivateKey(false, 1, 1000, 1000)
|
||||
val signature = crypto.getSignature(data, privateKey)
|
||||
assertThat(crypto.isSignatureValid(data, signature, privateKey.pubkey), `is`(true))
|
||||
assertTrue(crypto.isSignatureValid(data, signature, privateKey.pubkey))
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -146,18 +146,24 @@ class CryptographyTest {
|
||||
val privateKey = PrivateKey(false, 1, 1000, 1000)
|
||||
val signature = crypto.getSignature(data, privateKey)
|
||||
data[0]++
|
||||
assertThat(crypto.isSignatureValid(data, signature, privateKey.pubkey), `is`(false))
|
||||
assertFalse(crypto.isSignatureValid(data, signature, privateKey.pubkey))
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TEST_VALUE = "teststring".toByteArray()
|
||||
val TEST_SHA1 = DatatypeConverter.parseHexBinary(""
|
||||
+ "b8473b86d4c2072ca9b08bd28e373e8253e865c4")
|
||||
val TEST_SHA512 = DatatypeConverter.parseHexBinary(""
|
||||
+ "6253b39071e5df8b5098f59202d414c37a17d6a38a875ef5f8c7d89b0212b028"
|
||||
+ "692d3d2090ce03ae1de66c862fa8a561e57ed9eb7935ce627344f742c0931d72")
|
||||
val TEST_RIPEMD160 = DatatypeConverter.parseHexBinary(""
|
||||
+ "cd566972b5e50104011a92b59fa8e0b1234851ae")
|
||||
val TEST_SHA1 = DatatypeConverter.parseHexBinary(
|
||||
""
|
||||
+ "b8473b86d4c2072ca9b08bd28e373e8253e865c4"
|
||||
)
|
||||
val TEST_SHA512 = DatatypeConverter.parseHexBinary(
|
||||
""
|
||||
+ "6253b39071e5df8b5098f59202d414c37a17d6a38a875ef5f8c7d89b0212b028"
|
||||
+ "692d3d2090ce03ae1de66c862fa8a561e57ed9eb7935ce627344f742c0931d72"
|
||||
)
|
||||
val TEST_RIPEMD160 = DatatypeConverter.parseHexBinary(
|
||||
""
|
||||
+ "cd566972b5e50104011a92b59fa8e0b1234851ae"
|
||||
)
|
||||
|
||||
private val crypto = BouncyCryptography()
|
||||
|
||||
|
@ -13,7 +13,8 @@ uploadArchives {
|
||||
dependencies {
|
||||
compile project(':core')
|
||||
compile 'com.madgag.spongycastle:prov'
|
||||
testCompile 'junit:junit'
|
||||
testCompile 'com.nhaarman:mockito-kotlin'
|
||||
testCompile 'org.junit.jupiter:junit-jupiter-api'
|
||||
testRuntime 'org.junit.jupiter:junit-jupiter-engine'
|
||||
testCompile project(path: ':core', configuration: 'testArtifacts')
|
||||
}
|
||||
|
@ -68,12 +68,12 @@ class CryptographyTest {
|
||||
|
||||
@Test(expected = IOException::class)
|
||||
fun ensureExceptionForInsufficientProofOfWork() {
|
||||
val objectMessage = ObjectMessage.Builder()
|
||||
.nonce(ByteArray(8))
|
||||
.expiresTime(UnixTime.now + 28 * DAY)
|
||||
.objectType(0)
|
||||
.payload(GenericPayload.read(0, 1, ByteArrayInputStream(ByteArray(0)), 0))
|
||||
.build()
|
||||
val objectMessage = ObjectMessage(
|
||||
nonce = ByteArray(8),
|
||||
expiresTime = UnixTime.now + 28 * DAY,
|
||||
payload = GenericPayload.read(0, 1, ByteArrayInputStream(ByteArray(0)), 0),
|
||||
type = 0
|
||||
)
|
||||
crypto.checkProofOfWork(objectMessage, 1000, 1000)
|
||||
}
|
||||
|
||||
@ -86,10 +86,8 @@ class CryptographyTest {
|
||||
val objectMessage = ObjectMessage(
|
||||
nonce = ByteArray(8),
|
||||
expiresTime = UnixTime.now + 2 * MINUTE,
|
||||
type = 0,
|
||||
payload = GenericPayload.read(0, 1, ByteArrayInputStream(ByteArray(0)), 0),
|
||||
version = 0,
|
||||
stream = 1
|
||||
type = 0
|
||||
)
|
||||
val waiter = CallbackWaiter<ByteArray>()
|
||||
crypto.doProofOfWork(objectMessage, 1000, 1000,
|
||||
@ -154,13 +152,19 @@ class CryptographyTest {
|
||||
|
||||
companion object {
|
||||
val TEST_VALUE = "teststring".toByteArray()
|
||||
val TEST_SHA1 = DatatypeConverter.parseHexBinary(""
|
||||
+ "b8473b86d4c2072ca9b08bd28e373e8253e865c4")
|
||||
val TEST_SHA512 = DatatypeConverter.parseHexBinary(""
|
||||
+ "6253b39071e5df8b5098f59202d414c37a17d6a38a875ef5f8c7d89b0212b028"
|
||||
+ "692d3d2090ce03ae1de66c862fa8a561e57ed9eb7935ce627344f742c0931d72")
|
||||
val TEST_RIPEMD160 = DatatypeConverter.parseHexBinary(""
|
||||
+ "cd566972b5e50104011a92b59fa8e0b1234851ae")
|
||||
val TEST_SHA1 = DatatypeConverter.parseHexBinary(
|
||||
""
|
||||
+ "b8473b86d4c2072ca9b08bd28e373e8253e865c4"
|
||||
)
|
||||
val TEST_SHA512 = DatatypeConverter.parseHexBinary(
|
||||
""
|
||||
+ "6253b39071e5df8b5098f59202d414c37a17d6a38a875ef5f8c7d89b0212b028"
|
||||
+ "692d3d2090ce03ae1de66c862fa8a561e57ed9eb7935ce627344f742c0931d72"
|
||||
)
|
||||
val TEST_RIPEMD160 = DatatypeConverter.parseHexBinary(
|
||||
""
|
||||
+ "cd566972b5e50104011a92b59fa8e0b1234851ae"
|
||||
)
|
||||
|
||||
private val crypto = SpongyCryptography()
|
||||
|
||||
|
@ -24,15 +24,16 @@ task fatCapsule(type: FatCapsule) {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':core')
|
||||
compile project(':networking')
|
||||
compile project(':repositories')
|
||||
compile project(':cryptography-bc')
|
||||
compile project(':wif')
|
||||
compile 'org.slf4j:slf4j-simple'
|
||||
compile 'args4j:args4j'
|
||||
compile 'com.h2database:h2'
|
||||
compile 'org.apache.commons:commons-lang3'
|
||||
testCompile 'junit:junit'
|
||||
testCompile 'com.nhaarman:mockito-kotlin'
|
||||
implementation project(':core')
|
||||
implementation project(':networking')
|
||||
implementation project(':repositories')
|
||||
implementation project(':cryptography-bc')
|
||||
implementation project(':wif')
|
||||
implementation 'org.slf4j:slf4j-simple'
|
||||
implementation 'args4j:args4j'
|
||||
implementation 'com.h2database:h2'
|
||||
implementation 'org.apache.commons:commons-text'
|
||||
testImplementation 'com.nhaarman:mockito-kotlin'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api'
|
||||
testRuntime 'org.junit.jupiter:junit-jupiter-engine'
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ import ch.dissem.bitmessage.entity.Plaintext;
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
||||
import ch.dissem.bitmessage.entity.valueobject.Label;
|
||||
import ch.dissem.bitmessage.entity.valueobject.extended.Message;
|
||||
import org.apache.commons.lang3.text.WordUtils;
|
||||
import org.apache.commons.text.WordUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -115,6 +115,7 @@ public class Application {
|
||||
System.out.println(ctx.status());
|
||||
System.out.println();
|
||||
System.out.println("c) cleanup inventory");
|
||||
System.out.println("n) remove known nodes");
|
||||
System.out.println("r) resend unacknowledged messages");
|
||||
System.out.println(COMMAND_BACK);
|
||||
|
||||
@ -123,6 +124,9 @@ public class Application {
|
||||
case "c":
|
||||
ctx.cleanup();
|
||||
break;
|
||||
case "n":
|
||||
ctx.internals().getNodeRegistry().cleanup();
|
||||
break;
|
||||
case "r":
|
||||
ctx.resendUnacknowledgedMessages();
|
||||
break;
|
||||
@ -285,7 +289,7 @@ public class Application {
|
||||
}
|
||||
|
||||
private void labels() {
|
||||
List<Label> labels = ctx.messages().getLabels();
|
||||
List<Label> labels = ctx.labels().getLabels();
|
||||
String command;
|
||||
do {
|
||||
System.out.println();
|
||||
|
@ -24,6 +24,7 @@ import ch.dissem.bitmessage.ports.NodeRegistry;
|
||||
import ch.dissem.bitmessage.repository.*;
|
||||
import ch.dissem.bitmessage.wif.WifExporter;
|
||||
import ch.dissem.bitmessage.wif.WifImporter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.kohsuke.args4j.CmdLineException;
|
||||
import org.kohsuke.args4j.CmdLineParser;
|
||||
import org.kohsuke.args4j.Option;
|
||||
@ -62,18 +63,35 @@ public class Main {
|
||||
.inventory(new JdbcInventory(jdbcConfig))
|
||||
.messageRepo(new JdbcMessageRepository(jdbcConfig))
|
||||
.powRepo(new JdbcProofOfWorkRepository(jdbcConfig))
|
||||
.labelRepo(new JdbcLabelRepository(jdbcConfig))
|
||||
.networkHandler(new NioNetworkHandler())
|
||||
.cryptography(new BouncyCryptography())
|
||||
.port(48444);
|
||||
.cryptography(new BouncyCryptography());
|
||||
ctxBuilder.getPreferences().setPort(48444);
|
||||
if (options.localPort != null) {
|
||||
ctxBuilder.nodeRegistry(new NodeRegistry() {
|
||||
@Override
|
||||
public void cleanup() {
|
||||
// NO OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(@NotNull NetworkAddress node) {
|
||||
// NO OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull NetworkAddress node) {
|
||||
// NO OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
// NO OP
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<NetworkAddress> getKnownAddresses(int limit, long... streams) {
|
||||
public List<NetworkAddress> getKnownAddresses(int limit, @NotNull long... streams) {
|
||||
return Arrays.stream(streams)
|
||||
.mapToObj(s -> new NetworkAddress.Builder()
|
||||
.ipv4(127, 0, 0, 1)
|
||||
@ -83,7 +101,7 @@ public class Main {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offerAddresses(List<NetworkAddress> nodes) {
|
||||
public void offerAddresses(@NotNull List<NetworkAddress> nodes) {
|
||||
LOG.info("Local node registry ignored offered addresses: " + nodes);
|
||||
}
|
||||
});
|
||||
|
@ -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.ports.NodeRegistry;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@ -44,13 +45,29 @@ class TestNodeRegistry implements NodeRegistry {
|
||||
// NO OP
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<NetworkAddress> getKnownAddresses(int limit, long... streams) {
|
||||
public List<NetworkAddress> getKnownAddresses(int limit, @NotNull long... streams) {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offerAddresses(List<NetworkAddress> nodes) {
|
||||
public void offerAddresses(@NotNull List<NetworkAddress> nodes) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull NetworkAddress node) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(@NotNull NetworkAddress node) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
@ -15,9 +15,10 @@ dependencies {
|
||||
compile 'org.slf4j:slf4j-api'
|
||||
compile 'com.beust:klaxon'
|
||||
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile 'org.hamcrest:hamcrest-library:1.3'
|
||||
testCompile 'com.nhaarman:mockito-kotlin:1.5.0'
|
||||
testCompile 'com.nhaarman:mockito-kotlin'
|
||||
testCompile 'org.junit.jupiter:junit-jupiter-api'
|
||||
testRuntime 'org.junit.jupiter:junit-jupiter-engine'
|
||||
|
||||
testCompile project(path: ':core', configuration: 'testArtifacts')
|
||||
testCompile project(':cryptography-bc')
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ object ContactExport {
|
||||
"subscribed" to it.isSubscribed,
|
||||
"pubkey" to it.pubkey?.let {
|
||||
val out = ByteArrayOutputStream()
|
||||
it.writeUnencrypted(out)
|
||||
it.writer().writeUnencrypted(out)
|
||||
Base64.encodeToString(out.toByteArray())
|
||||
},
|
||||
"privateKey" to if (includePrivateKey) {
|
||||
@ -68,7 +68,7 @@ object ContactExport {
|
||||
Factory.readPubkey(
|
||||
version = version,
|
||||
stream = stream,
|
||||
`is` = ByteArrayInputStream(it),
|
||||
input = ByteArrayInputStream(it),
|
||||
length = it.size,
|
||||
encrypted = false
|
||||
)
|
||||
|
@ -19,10 +19,8 @@ package ch.dissem.bitmessage.exports
|
||||
import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||
import ch.dissem.bitmessage.utils.TestUtils
|
||||
import org.hamcrest.CoreMatchers.`is`
|
||||
import org.hamcrest.CoreMatchers.nullValue
|
||||
import org.junit.Assert.assertThat
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Assertions.*
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class ContactExportTest {
|
||||
|
||||
@ -42,7 +40,7 @@ class ContactExportTest {
|
||||
)
|
||||
val export = ContactExport.exportContacts(contacts)
|
||||
print(export.toJsonString(true))
|
||||
assertThat(ContactExport.importContacts(export), `is`(contacts))
|
||||
assertEquals(contacts, ContactExport.importContacts(export))
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -53,9 +51,9 @@ class ContactExportTest {
|
||||
val export = ContactExport.exportContacts(contacts)
|
||||
print(export.toJsonString(true))
|
||||
val import = ContactExport.importContacts(export)
|
||||
assertThat(import.size, `is`(1))
|
||||
assertThat(import[0].isChan, `is`(true))
|
||||
assertThat(import[0].privateKey, `is`(nullValue()))
|
||||
assertEquals(1, import.size)
|
||||
assertTrue(import[0].isChan)
|
||||
assertNull(import[0].privateKey)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -66,8 +64,9 @@ class ContactExportTest {
|
||||
val export = ContactExport.exportContacts(contacts, true)
|
||||
print(export.toJsonString(true))
|
||||
val import = ContactExport.importContacts(export)
|
||||
assertThat(import.size, `is`(1))
|
||||
assertThat(import[0].isChan, `is`(true))
|
||||
assertThat(import[0].privateKey, `is`(contacts[0].privateKey))
|
||||
|
||||
assertEquals(1, import.size)
|
||||
assertTrue(import[0].isChan)
|
||||
assertEquals(contacts[0].privateKey, import[0].privateKey)
|
||||
}
|
||||
}
|
||||
|
@ -23,23 +23,22 @@ import ch.dissem.bitmessage.entity.valueobject.Label
|
||||
import ch.dissem.bitmessage.utils.ConversationServiceTest
|
||||
import ch.dissem.bitmessage.utils.Singleton
|
||||
import ch.dissem.bitmessage.utils.TestUtils
|
||||
import org.hamcrest.CoreMatchers.`is`
|
||||
import org.junit.Assert.assertThat
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class MessageExportTest {
|
||||
val inbox = Label("Inbox", Label.Type.INBOX, 0x0000ff)
|
||||
val outbox = Label("Outbox", Label.Type.OUTBOX, 0x00ff00)
|
||||
val unread = Label("Unread", Label.Type.UNREAD, 0x000000)
|
||||
val trash = Label("Trash", Label.Type.TRASH, 0x555555)
|
||||
private val inbox = Label("Inbox", Label.Type.INBOX, 0x0000ff)
|
||||
private val outbox = Label("Outbox", Label.Type.OUTBOX, 0x00ff00)
|
||||
private val unread = Label("Unread", Label.Type.UNREAD, 0x000000)
|
||||
private val trash = Label("Trash", Label.Type.TRASH, 0x555555)
|
||||
|
||||
val labels = listOf(
|
||||
private val labels = listOf(
|
||||
inbox,
|
||||
outbox,
|
||||
unread,
|
||||
trash
|
||||
)
|
||||
val labelMap = MessageExport.createLabelMap(labels)
|
||||
private val labelMap = MessageExport.createLabelMap(labels)
|
||||
|
||||
init {
|
||||
TestUtils.mockedInternalContext(cryptography = BouncyCryptography())
|
||||
@ -49,7 +48,7 @@ class MessageExportTest {
|
||||
fun `ensure labels are exported`() {
|
||||
val export = MessageExport.exportLabels(labels)
|
||||
print(export.toJsonString(true))
|
||||
assertThat(MessageExport.importLabels(export), `is`(labels))
|
||||
assertEquals(labels, MessageExport.importLabels(export))
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -84,6 +83,6 @@ class MessageExportTest {
|
||||
)
|
||||
val export = MessageExport.exportMessages(messages)
|
||||
print(export.toJsonString(true))
|
||||
assertThat(MessageExport.importMessages(export, labelMap), `is`(messages))
|
||||
assertEquals(messages, MessageExport.importMessages(export, labelMap))
|
||||
}
|
||||
}
|
||||
|
@ -28,9 +28,10 @@ uploadArchives {
|
||||
|
||||
dependencies {
|
||||
compile project(':core')
|
||||
testCompile 'junit:junit'
|
||||
testCompile 'org.slf4j:slf4j-simple'
|
||||
testCompile 'com.nhaarman:mockito-kotlin'
|
||||
testCompile 'org.junit.jupiter:junit-jupiter-api'
|
||||
testRuntime 'org.junit.jupiter:junit-jupiter-engine'
|
||||
testCompile project(path: ':core', configuration: 'testArtifacts')
|
||||
testCompile project(':cryptography-bc')
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ package ch.dissem.bitmessage.extensions
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||
import ch.dissem.bitmessage.entity.CustomMessage
|
||||
import ch.dissem.bitmessage.entity.Streamable
|
||||
import ch.dissem.bitmessage.entity.StreamableWriter
|
||||
import ch.dissem.bitmessage.entity.payload.CryptoBox
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
||||
@ -41,19 +42,21 @@ import java.io.OutputStream
|
||||
*/
|
||||
class CryptoCustomMessage<T : Streamable> : CustomMessage {
|
||||
|
||||
private val dataReader: Reader<T>?
|
||||
private val dataReader: (BitmessageAddress, InputStream) -> T
|
||||
private var container: CryptoBox? = null
|
||||
var sender: BitmessageAddress? = null
|
||||
private set
|
||||
private var data: T? = null
|
||||
private set
|
||||
|
||||
constructor(data: T) : super(COMMAND, null) {
|
||||
this.data = data
|
||||
this.dataReader = null
|
||||
this.dataReader = { _, _ -> data }
|
||||
}
|
||||
|
||||
private constructor(container: CryptoBox, dataReader: Reader<T>) : super(COMMAND, null) {
|
||||
private constructor(container: CryptoBox, dataReader: (BitmessageAddress, InputStream) -> T) : super(
|
||||
COMMAND,
|
||||
null
|
||||
) {
|
||||
this.container = container
|
||||
this.dataReader = dataReader
|
||||
}
|
||||
@ -73,52 +76,64 @@ class CryptoCustomMessage<T : Streamable> : CustomMessage {
|
||||
Encode.varInt(privateKey.pubkey.extraBytes, out)
|
||||
}
|
||||
|
||||
data?.write(out) ?: throw IllegalStateException("no unencrypted data available")
|
||||
data?.writer()?.write(out) ?: throw IllegalStateException("no unencrypted data available")
|
||||
Encode.varBytes(cryptography().getSignature(out.toByteArray(), privateKey), out)
|
||||
container = CryptoBox(out.toByteArray(), publicKey)
|
||||
}
|
||||
|
||||
@Throws(DecryptionFailedException::class)
|
||||
fun decrypt(privateKey: ByteArray): T {
|
||||
val `in` = SignatureCheckingInputStream(container?.decrypt(privateKey) ?: throw IllegalStateException("no encrypted data available"))
|
||||
if (dataReader == null) throw IllegalStateException("no data reader available")
|
||||
val input = SignatureCheckingInputStream(
|
||||
container?.decrypt(privateKey) ?: throw IllegalStateException("no encrypted data available")
|
||||
)
|
||||
|
||||
val addressVersion = varInt(`in`)
|
||||
val stream = varInt(`in`)
|
||||
val behaviorBitfield = int32(`in`)
|
||||
val publicSigningKey = bytes(`in`, 64)
|
||||
val publicEncryptionKey = bytes(`in`, 64)
|
||||
val nonceTrialsPerByte = if (addressVersion >= 3) varInt(`in`) else 0
|
||||
val extraBytes = if (addressVersion >= 3) varInt(`in`) else 0
|
||||
val addressVersion = varInt(input)
|
||||
val stream = varInt(input)
|
||||
val behaviorBitfield = int32(input)
|
||||
val publicSigningKey = bytes(input, 64)
|
||||
val publicEncryptionKey = bytes(input, 64)
|
||||
val nonceTrialsPerByte = if (addressVersion >= 3) varInt(input) else 0
|
||||
val extraBytes = if (addressVersion >= 3) varInt(input) else 0
|
||||
|
||||
val sender = BitmessageAddress(Factory.createPubkey(
|
||||
addressVersion,
|
||||
stream,
|
||||
publicSigningKey,
|
||||
publicEncryptionKey,
|
||||
nonceTrialsPerByte,
|
||||
extraBytes,
|
||||
behaviorBitfield
|
||||
))
|
||||
val sender = BitmessageAddress(
|
||||
Factory.createPubkey(
|
||||
addressVersion,
|
||||
stream,
|
||||
publicSigningKey,
|
||||
publicEncryptionKey,
|
||||
nonceTrialsPerByte,
|
||||
extraBytes,
|
||||
behaviorBitfield
|
||||
)
|
||||
)
|
||||
this.sender = sender
|
||||
|
||||
data = dataReader.read(sender, `in`)
|
||||
data = dataReader.invoke(sender, input)
|
||||
|
||||
`in`.checkSignature(sender.pubkey!!)
|
||||
input.checkSignature(sender.pubkey!!)
|
||||
|
||||
return data!!
|
||||
}
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
Encode.varString(COMMAND, out)
|
||||
container?.write(out) ?: throw IllegalStateException("not encrypted yet")
|
||||
override fun writer(): StreamableWriter = Writer(this)
|
||||
|
||||
private class Writer(
|
||||
private val item: CryptoCustomMessage<*>
|
||||
) : CustomMessage.Writer(item) {
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
Encode.varString(COMMAND, out)
|
||||
item.container?.writer()?.write(out) ?: throw IllegalStateException("not encrypted yet")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface Reader<out T> {
|
||||
fun read(sender: BitmessageAddress, `in`: InputStream): T
|
||||
fun read(sender: BitmessageAddress, input: InputStream): T
|
||||
}
|
||||
|
||||
private inner class SignatureCheckingInputStream internal constructor(private val wrapped: InputStream) : InputStream() {
|
||||
private inner class SignatureCheckingInputStream internal constructor(private val wrapped: InputStream) :
|
||||
InputStream() {
|
||||
private val out = ByteArrayOutputStream()
|
||||
|
||||
override fun read(): Int {
|
||||
@ -136,11 +151,24 @@ class CryptoCustomMessage<T : Streamable> : CustomMessage {
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmField val COMMAND = "ENCRYPTED"
|
||||
const val COMMAND = "ENCRYPTED"
|
||||
|
||||
@JvmStatic fun <T : Streamable> read(data: CustomMessage, dataReader: Reader<T>): CryptoCustomMessage<T> {
|
||||
val cryptoBox = CryptoBox.read(ByteArrayInputStream(data.getData()), data.getData().size)
|
||||
return CryptoCustomMessage(cryptoBox, dataReader)
|
||||
}
|
||||
@JvmStatic
|
||||
fun <T : Streamable> read(data: CustomMessage, dataReader: Reader<T>): CryptoCustomMessage<T> =
|
||||
CryptoCustomMessage(
|
||||
CryptoBox.read(ByteArrayInputStream(data.getData()), data.getData().size)
|
||||
) { address, input ->
|
||||
dataReader.read(address, input)
|
||||
}
|
||||
|
||||
@JvmSynthetic
|
||||
fun <T : Streamable> read(
|
||||
data: CustomMessage,
|
||||
dataReader: (BitmessageAddress, InputStream) -> T
|
||||
): CryptoCustomMessage<T> =
|
||||
CryptoCustomMessage(
|
||||
CryptoBox.read(ByteArrayInputStream(data.getData()), data.getData().size),
|
||||
dataReader
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ package ch.dissem.bitmessage.extensions.pow
|
||||
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||
import ch.dissem.bitmessage.entity.Streamable
|
||||
import ch.dissem.bitmessage.entity.StreamableWriter
|
||||
import ch.dissem.bitmessage.extensions.CryptoCustomMessage
|
||||
import ch.dissem.bitmessage.utils.Decode.bytes
|
||||
import ch.dissem.bitmessage.utils.Decode.varBytes
|
||||
@ -33,22 +34,30 @@ import java.util.*
|
||||
*/
|
||||
data class ProofOfWorkRequest @JvmOverloads constructor(val sender: BitmessageAddress, val initialHash: ByteArray, val request: ProofOfWorkRequest.Request, val data: ByteArray = ByteArray(0)) : Streamable {
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
out.write(initialHash)
|
||||
Encode.varString(request.name, out)
|
||||
Encode.varBytes(data, out)
|
||||
}
|
||||
override fun writer(): StreamableWriter = Writer(this)
|
||||
|
||||
private class Writer(
|
||||
private val item: ProofOfWorkRequest
|
||||
) : StreamableWriter {
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
out.write(item.initialHash)
|
||||
Encode.varString(item.request.name, out)
|
||||
Encode.varBytes(item.data, out)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
buffer.put(item.initialHash)
|
||||
Encode.varString(item.request.name, buffer)
|
||||
Encode.varBytes(item.data, buffer)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
buffer.put(initialHash)
|
||||
Encode.varString(request.name, buffer)
|
||||
Encode.varBytes(data, buffer)
|
||||
}
|
||||
|
||||
class Reader(private val identity: BitmessageAddress) : CryptoCustomMessage.Reader<ProofOfWorkRequest> {
|
||||
|
||||
override fun read(sender: BitmessageAddress, `in`: InputStream): ProofOfWorkRequest {
|
||||
return ProofOfWorkRequest.read(identity, `in`)
|
||||
override fun read(sender: BitmessageAddress, input: InputStream): ProofOfWorkRequest {
|
||||
return ProofOfWorkRequest.read(identity, input)
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,12 +87,12 @@ data class ProofOfWorkRequest @JvmOverloads constructor(val sender: BitmessageAd
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun read(client: BitmessageAddress, `in`: InputStream): ProofOfWorkRequest {
|
||||
fun read(client: BitmessageAddress, input: InputStream): ProofOfWorkRequest {
|
||||
return ProofOfWorkRequest(
|
||||
client,
|
||||
bytes(`in`, 64),
|
||||
Request.valueOf(varString(`in`)),
|
||||
varBytes(`in`)
|
||||
bytes(input, 64),
|
||||
Request.valueOf(varString(input)),
|
||||
varBytes(input)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -21,17 +21,15 @@ import ch.dissem.bitmessage.entity.CustomMessage
|
||||
import ch.dissem.bitmessage.entity.payload.GenericPayload
|
||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
|
||||
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest
|
||||
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||
import ch.dissem.bitmessage.utils.TestBase
|
||||
import ch.dissem.bitmessage.utils.TestUtils
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.InputStream
|
||||
|
||||
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||
import org.junit.Assert.assertEquals
|
||||
|
||||
class CryptoCustomMessageTest : TestBase() {
|
||||
@Test
|
||||
fun `ensure encrypt then decrypt yields same object`() {
|
||||
@ -40,19 +38,21 @@ class CryptoCustomMessageTest : TestBase() {
|
||||
|
||||
val payloadBefore = GenericPayload(0, 1, cryptography().randomBytes(100))
|
||||
val messageBefore = CryptoCustomMessage(payloadBefore)
|
||||
messageBefore.signAndEncrypt(sendingIdentity, cryptography().createPublicKey(sendingIdentity.publicDecryptionKey))
|
||||
messageBefore.signAndEncrypt(
|
||||
sendingIdentity,
|
||||
cryptography().createPublicKey(sendingIdentity.publicDecryptionKey)
|
||||
)
|
||||
|
||||
val out = ByteArrayOutputStream()
|
||||
messageBefore.write(out)
|
||||
val `in` = ByteArrayInputStream(out.toByteArray())
|
||||
messageBefore.writer().write(out)
|
||||
val input = ByteArrayInputStream(out.toByteArray())
|
||||
|
||||
val customMessage = CustomMessage.read(`in`, out.size())
|
||||
val customMessage = CustomMessage.read(input, out.size())
|
||||
val messageAfter = CryptoCustomMessage.read(customMessage,
|
||||
object : CryptoCustomMessage.Reader<GenericPayload> {
|
||||
override fun read(sender: BitmessageAddress, `in`: InputStream): GenericPayload {
|
||||
return GenericPayload.read(0, 1, `in`, 100)
|
||||
}
|
||||
})
|
||||
object : CryptoCustomMessage.Reader<GenericPayload> {
|
||||
override fun read(sender: BitmessageAddress, input: InputStream) =
|
||||
GenericPayload.read(0, 1, input, 100)
|
||||
})
|
||||
val payloadAfter = messageAfter.decrypt(sendingIdentity.publicDecryptionKey)
|
||||
|
||||
assertEquals(payloadBefore, payloadAfter)
|
||||
@ -63,20 +63,27 @@ class CryptoCustomMessageTest : TestBase() {
|
||||
val privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey"))
|
||||
val sendingIdentity = BitmessageAddress(privateKey)
|
||||
|
||||
val requestBefore = ProofOfWorkRequest(sendingIdentity, cryptography().randomBytes(64),
|
||||
ProofOfWorkRequest.Request.CALCULATE)
|
||||
val requestBefore = ProofOfWorkRequest(
|
||||
sendingIdentity, cryptography().randomBytes(64),
|
||||
ProofOfWorkRequest.Request.CALCULATE
|
||||
)
|
||||
|
||||
val messageBefore = CryptoCustomMessage(requestBefore)
|
||||
messageBefore.signAndEncrypt(sendingIdentity, cryptography().createPublicKey(sendingIdentity.publicDecryptionKey))
|
||||
messageBefore.signAndEncrypt(
|
||||
sendingIdentity,
|
||||
cryptography().createPublicKey(sendingIdentity.publicDecryptionKey)
|
||||
)
|
||||
|
||||
|
||||
val out = ByteArrayOutputStream()
|
||||
messageBefore.write(out)
|
||||
val `in` = ByteArrayInputStream(out.toByteArray())
|
||||
messageBefore.writer().write(out)
|
||||
val input = ByteArrayInputStream(out.toByteArray())
|
||||
|
||||
val customMessage = CustomMessage.read(`in`, out.size())
|
||||
val messageAfter = CryptoCustomMessage.read(customMessage,
|
||||
ProofOfWorkRequest.Reader(sendingIdentity))
|
||||
val customMessage = CustomMessage.read(input, out.size())
|
||||
val messageAfter = CryptoCustomMessage.read(
|
||||
customMessage,
|
||||
ProofOfWorkRequest.Reader(sendingIdentity)
|
||||
)
|
||||
val requestAfter = messageAfter.decrypt(sendingIdentity.publicDecryptionKey)
|
||||
|
||||
assertEquals(requestBefore, requestAfter)
|
||||
|
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
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
|
||||
|
@ -12,9 +12,10 @@ uploadArchives {
|
||||
|
||||
dependencies {
|
||||
compile project(':core')
|
||||
testCompile 'junit:junit'
|
||||
testCompile 'org.slf4j:slf4j-simple'
|
||||
testCompile 'com.nhaarman:mockito-kotlin'
|
||||
testCompile 'org.junit.jupiter:junit-jupiter-api'
|
||||
testRuntime 'org.junit.jupiter:junit-jupiter-engine'
|
||||
testCompile project(path: ':core', configuration: 'testArtifacts')
|
||||
testCompile project(':cryptography-bc')
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ import org.slf4j.LoggerFactory
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Contains everything used by both the old streams-oriented NetworkHandler and the new NioNetworkHandler,
|
||||
@ -58,7 +59,7 @@ class Connection(
|
||||
private var lastObjectTime: Long = 0
|
||||
|
||||
lateinit var streams: LongArray
|
||||
protected set
|
||||
private set
|
||||
|
||||
@Volatile var state = State.CONNECTING
|
||||
private set
|
||||
@ -165,11 +166,12 @@ class Connection(
|
||||
}
|
||||
}
|
||||
|
||||
// the TCP timeout starts out at 20 seconds
|
||||
// According to the specification, the TCP timeout starts out at 20 seconds
|
||||
// after verack messages are exchanged, the timeout is raised to 10 minutes
|
||||
// Let's tweak these numbers a bit:
|
||||
fun isExpired(): Boolean = when (state) {
|
||||
State.CONNECTING -> io.lastUpdate < System.currentTimeMillis() - 20000
|
||||
State.ACTIVE -> io.lastUpdate < System.currentTimeMillis() - 600000
|
||||
State.CONNECTING -> io.lastUpdate < System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(9)
|
||||
State.ACTIVE -> io.lastUpdate < System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(3)
|
||||
State.DISCONNECTED -> true
|
||||
}
|
||||
|
||||
|
@ -16,11 +16,11 @@
|
||||
|
||||
package ch.dissem.bitmessage.networking.nio
|
||||
|
||||
import ch.dissem.bitmessage.constants.Network.HEADER_SIZE
|
||||
import ch.dissem.bitmessage.entity.GetData
|
||||
import ch.dissem.bitmessage.entity.MessagePayload
|
||||
import ch.dissem.bitmessage.entity.NetworkMessage
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
||||
import ch.dissem.bitmessage.exception.NodeException
|
||||
import ch.dissem.bitmessage.factory.V3MessageReader
|
||||
import ch.dissem.bitmessage.utils.UnixTime
|
||||
import org.slf4j.LoggerFactory
|
||||
@ -39,9 +39,9 @@ class ConnectionIO(
|
||||
private val getState: () -> Connection.State,
|
||||
private val handleMessage: (MessagePayload) -> Unit
|
||||
) {
|
||||
private val headerOut: ByteBuffer = ByteBuffer.allocate(24)
|
||||
private val headerOut: ByteBuffer = ByteBuffer.allocate(HEADER_SIZE)
|
||||
private var payloadOut: ByteBuffer? = null
|
||||
private var reader: V3MessageReader? = V3MessageReader()
|
||||
private val reader = V3MessageReader()
|
||||
internal val sendingQueue: Deque<MessagePayload> = ConcurrentLinkedDeque<MessagePayload>()
|
||||
|
||||
internal var lastUpdate = System.currentTimeMillis()
|
||||
@ -54,14 +54,13 @@ class ConnectionIO(
|
||||
headerOut.flip()
|
||||
}
|
||||
|
||||
val inBuffer: ByteBuffer
|
||||
get() = reader?.getActiveBuffer() ?: throw NodeException("Node is disconnected")
|
||||
val inBuffer: ByteBuffer = reader.buffer
|
||||
|
||||
fun updateWriter() {
|
||||
if (!headerOut.hasRemaining() && !sendingQueue.isEmpty()) {
|
||||
headerOut.clear()
|
||||
val payload = sendingQueue.poll()
|
||||
payloadOut = NetworkMessage(payload).writeHeaderAndGetPayloadBuffer(headerOut)
|
||||
payloadOut = NetworkMessage(payload).writer().writeHeaderAndGetPayloadBuffer(headerOut)
|
||||
headerOut.flip()
|
||||
lastUpdate = System.currentTimeMillis()
|
||||
}
|
||||
@ -77,7 +76,7 @@ class ConnectionIO(
|
||||
}
|
||||
|
||||
fun updateReader() {
|
||||
reader?.let { reader ->
|
||||
reader.let { reader ->
|
||||
reader.update()
|
||||
if (!reader.getMessages().isEmpty()) {
|
||||
val iterator = reader.getMessages().iterator()
|
||||
@ -95,11 +94,11 @@ class ConnectionIO(
|
||||
|
||||
fun updateSyncStatus() {
|
||||
if (!isSyncFinished) {
|
||||
isSyncFinished = reader?.getMessages()?.isEmpty() ?: true && syncFinished(null)
|
||||
isSyncFinished = reader.getMessages().isEmpty() && syncFinished(null)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun syncFinished(msg: NetworkMessage?): Boolean {
|
||||
private fun syncFinished(msg: NetworkMessage?): Boolean {
|
||||
if (mode != Connection.Mode.SYNC) {
|
||||
return false
|
||||
}
|
||||
@ -126,10 +125,6 @@ class ConnectionIO(
|
||||
}
|
||||
|
||||
fun disconnect() {
|
||||
reader?.let {
|
||||
it.cleanup()
|
||||
reader = null
|
||||
}
|
||||
payloadOut = null
|
||||
}
|
||||
|
||||
@ -150,7 +145,7 @@ class ConnectionIO(
|
||||
|| headerOut.hasRemaining()
|
||||
|| payloadOut?.hasRemaining() ?: false
|
||||
|
||||
fun nothingToSend() = sendingQueue.isEmpty()
|
||||
private fun nothingToSend() = sendingQueue.isEmpty()
|
||||
|
||||
companion object {
|
||||
val LOG = LoggerFactory.getLogger(ConnectionIO::class.java)
|
||||
|
@ -41,7 +41,7 @@ class NetworkConnectionInitializer(
|
||||
|
||||
fun start() {
|
||||
if (mode == Connection.Mode.CLIENT || mode == Connection.Mode.SYNC) {
|
||||
send(Version(nonce = ctx.clientNonce, addrFrom = NetworkAddress.ANY, addrRecv = node, userAgent = ctx.userAgent))
|
||||
send(Version(nonce = ctx.clientNonce, addrFrom = NetworkAddress.ANY, addrRecv = node, userAgent = ctx.preferences.userAgent))
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,12 +77,12 @@ class NetworkConnectionInitializer(
|
||||
activateConnection()
|
||||
}
|
||||
} else {
|
||||
throw NodeException("Received unsupported version " + version.version + ", disconnecting.")
|
||||
throw NodeException("Received unsupported version ${version.version}, disconnecting.")
|
||||
}
|
||||
}
|
||||
|
||||
private fun activateConnection() {
|
||||
LOG.info("Successfully established connection with node " + node)
|
||||
LOG.info("Successfully established connection with node $node")
|
||||
markActive(version.streams)
|
||||
node.time = UnixTime.now
|
||||
if (mode != Connection.Mode.SYNC) {
|
||||
|
@ -34,6 +34,7 @@ import ch.dissem.bitmessage.utils.Property
|
||||
import ch.dissem.bitmessage.utils.ThreadFactoryBuilder.Companion.pool
|
||||
import ch.dissem.bitmessage.utils.UnixTime.now
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.Closeable
|
||||
import java.io.IOException
|
||||
import java.net.InetAddress
|
||||
import java.net.InetSocketAddress
|
||||
@ -47,20 +48,22 @@ import java.util.concurrent.*
|
||||
/**
|
||||
* Network handler using java.nio, resulting in less threads.
|
||||
*/
|
||||
class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
||||
class NioNetworkHandler(private val magicNetworkNumber: Int = NETWORK_MAGIC_NUMBER) : NetworkHandler,
|
||||
InternalContext.ContextHolder {
|
||||
|
||||
private val threadPool = Executors.newCachedThreadPool(
|
||||
pool("network")
|
||||
.lowPrio()
|
||||
.daemon()
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
|
||||
private lateinit var ctx: InternalContext
|
||||
private var selector: Selector? = null
|
||||
private var serverChannel: ServerSocketChannel? = null
|
||||
private val connectionQueue = ConcurrentLinkedQueue<NetworkAddress>()
|
||||
private val connections = ConcurrentHashMap<Connection, SelectionKey>()
|
||||
private val requestedObjects = ConcurrentHashMap<InventoryVector, Long>(10000)
|
||||
private val connections: MutableMap<Connection, SelectionKey> = ConcurrentHashMap()
|
||||
private val requestedObjects: MutableMap<InventoryVector, Long> = ConcurrentHashMap(10000)
|
||||
|
||||
private var starter: Thread? = null
|
||||
|
||||
@ -72,9 +75,11 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
||||
return threadPool.submit(Callable<Void> {
|
||||
SocketChannel.open(InetSocketAddress(server, port)).use { channel ->
|
||||
channel.configureBlocking(false)
|
||||
val connection = Connection(ctx, SYNC,
|
||||
val connection = Connection(
|
||||
ctx, SYNC,
|
||||
NetworkAddress.Builder().ip(server).port(port).stream(1).build(),
|
||||
HashMap<InventoryVector, Long>(), timeoutInSeconds)
|
||||
HashMap(), timeoutInSeconds
|
||||
)
|
||||
while (channel.isConnected && !connection.isSyncFinished) {
|
||||
write(channel, connection.io)
|
||||
read(channel, connection.io)
|
||||
@ -90,7 +95,7 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
||||
SocketChannel.open(InetSocketAddress(server, port)).use { channel ->
|
||||
channel.configureBlocking(true)
|
||||
val headerBuffer = ByteBuffer.allocate(HEADER_SIZE)
|
||||
val payloadBuffer = NetworkMessage(request).writeHeaderAndGetPayloadBuffer(headerBuffer)
|
||||
val payloadBuffer = NetworkMessage(request).writer().writeHeaderAndGetPayloadBuffer(headerBuffer)
|
||||
headerBuffer.flip()
|
||||
while (headerBuffer.hasRemaining()) {
|
||||
channel.write(headerBuffer)
|
||||
@ -101,7 +106,7 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
||||
|
||||
val reader = V3MessageReader()
|
||||
while (channel.isConnected && reader.getMessages().isEmpty()) {
|
||||
if (channel.read(reader.getActiveBuffer()) > 0) {
|
||||
if (channel.read(reader.buffer) > 0) {
|
||||
reader.update()
|
||||
} else {
|
||||
throw NodeException("No response from node $server")
|
||||
@ -109,7 +114,7 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
||||
}
|
||||
val networkMessage: NetworkMessage?
|
||||
if (reader.getMessages().isEmpty()) {
|
||||
throw NodeException("No response from node " + server)
|
||||
throw NodeException("No response from node $server")
|
||||
} else {
|
||||
networkMessage = reader.getMessages().first()
|
||||
}
|
||||
@ -123,7 +128,7 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
if (selector?.isOpen ?: false) {
|
||||
if (selector?.isOpen == true) {
|
||||
throw IllegalStateException("Network already running - you need to stop first.")
|
||||
}
|
||||
val selector = Selector.open()
|
||||
@ -133,7 +138,7 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
||||
|
||||
starter = thread("connection manager") {
|
||||
while (selector.isOpen) {
|
||||
var missing = NETWORK_MAGIC_NUMBER
|
||||
var missing = magicNetworkNumber
|
||||
for ((connection, _) in connections) {
|
||||
if (connection.state == Connection.State.ACTIVE) {
|
||||
missing--
|
||||
@ -187,19 +192,19 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
||||
request(delayed)
|
||||
|
||||
try {
|
||||
Thread.sleep(30000)
|
||||
Thread.sleep(10000)
|
||||
} catch (e: InterruptedException) {
|
||||
return@thread
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
thread("selector worker", {
|
||||
thread("selector worker") {
|
||||
try {
|
||||
val serverChannel = ServerSocketChannel.open()
|
||||
this.serverChannel = serverChannel
|
||||
serverChannel.configureBlocking(false)
|
||||
serverChannel.socket().bind(InetSocketAddress(ctx.port))
|
||||
serverChannel.socket().bind(InetSocketAddress(ctx.preferences.port))
|
||||
serverChannel.register(selector, OP_ACCEPT, null)
|
||||
|
||||
while (selector.isOpen) {
|
||||
@ -216,7 +221,8 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
||||
try {
|
||||
val accepted = (key.channel() as ServerSocketChannel).accept()
|
||||
accepted.configureBlocking(false)
|
||||
val connection = Connection(ctx, SERVER,
|
||||
val connection = Connection(
|
||||
ctx, SERVER,
|
||||
NetworkAddress(
|
||||
time = now,
|
||||
stream = 1L,
|
||||
@ -224,10 +230,8 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
||||
),
|
||||
requestedObjects, 0
|
||||
)
|
||||
connections.put(
|
||||
connection,
|
||||
connections[connection] =
|
||||
accepted.register(selector, OP_READ or OP_WRITE, connection)
|
||||
)
|
||||
} catch (e: AsynchronousCloseException) {
|
||||
LOG.trace(e.message)
|
||||
} catch (e: IOException) {
|
||||
@ -255,19 +259,22 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
||||
if (key.isReadable) {
|
||||
read(channel, connection.io)
|
||||
}
|
||||
if (connection.state == Connection.State.DISCONNECTED) {
|
||||
key.interestOps(0)
|
||||
channel.close()
|
||||
} else if (connection.io.isWritePending) {
|
||||
key.interestOps(OP_READ or OP_WRITE)
|
||||
} else {
|
||||
key.interestOps(OP_READ)
|
||||
when {
|
||||
connection.state == Connection.State.DISCONNECTED -> {
|
||||
key.interestOps(0)
|
||||
channel.close()
|
||||
}
|
||||
connection.io.isWritePending -> key.interestOps(OP_READ or OP_WRITE)
|
||||
else -> key.interestOps(OP_READ)
|
||||
}
|
||||
} catch (e: CancelledKeyException) {
|
||||
LOG.debug("${e.message}: ${connection.node}", e)
|
||||
connection.disconnect()
|
||||
} catch (e: NodeException) {
|
||||
LOG.debug("${e.message}: ${connection.node}", e)
|
||||
connection.disconnect()
|
||||
} catch (e: IOException) {
|
||||
LOG.debug("${e.message}: ${connection.node}", e)
|
||||
connection.disconnect()
|
||||
}
|
||||
}
|
||||
@ -281,7 +288,8 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
||||
if (selectionKey.isValid
|
||||
&& selectionKey.interestOps() and OP_WRITE == 0
|
||||
&& selectionKey.interestOps() and OP_CONNECT == 0
|
||||
&& !connection.nothingToSend) {
|
||||
&& !connection.nothingToSend
|
||||
) {
|
||||
selectionKey.interestOps(OP_READ or OP_WRITE)
|
||||
}
|
||||
} catch (x: CancelledKeyException) {
|
||||
@ -297,10 +305,11 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
||||
channel.configureBlocking(false)
|
||||
channel.connect(InetSocketAddress(address.toInetAddress(), address.port))
|
||||
val connection = Connection(ctx, CLIENT, address, requestedObjects, 0)
|
||||
connections.put(
|
||||
connection,
|
||||
channel.register(selector, OP_CONNECT, connection)
|
||||
)
|
||||
|
||||
connections[connection] = channel.register(selector, OP_CONNECT, connection)
|
||||
|
||||
LOG.debug("Connection registered to $address")
|
||||
|
||||
} catch (ignore: NoRouteToHostException) {
|
||||
// We'll try to connect to many offline nodes, so
|
||||
// this is expected to happen quite a lot.
|
||||
@ -312,7 +321,11 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
||||
LOG.error(e.message, e)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
LOG.error(e.message, e)
|
||||
if (e.message == "Network is unreachable") {
|
||||
LOG.debug("Network is unreachable: $address")
|
||||
} else {
|
||||
LOG.error("${e.message}: $address", e)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -326,7 +339,7 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
||||
// isn't nice though.
|
||||
LOG.error(e.message, e)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun thread(threadName: String, runnable: () -> Unit): Thread {
|
||||
@ -338,16 +351,24 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
serverChannel?.socket()?.close()
|
||||
selector?.close()
|
||||
tryClose(serverChannel?.socket())
|
||||
tryClose(selector)
|
||||
for (selectionKey in connections.values) {
|
||||
selectionKey.channel().close()
|
||||
tryClose(selectionKey.channel())
|
||||
}
|
||||
}
|
||||
|
||||
private fun tryClose(item: Closeable?) {
|
||||
try {
|
||||
item?.close()
|
||||
} catch (e: IOException) {
|
||||
LOG.debug(e.message, e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun offer(iv: InventoryVector) {
|
||||
val targetConnections = connections.keys.filter { it.state == Connection.State.ACTIVE && !it.knowsOf(iv) }
|
||||
selectRandom(NETWORK_MAGIC_NUMBER, targetConnections).forEach { it.offer(iv) }
|
||||
selectRandom(magicNetworkNumber, targetConnections).forEach { it.offer(iv) }
|
||||
}
|
||||
|
||||
override fun request(inventoryVectors: MutableCollection<InventoryVector>) {
|
||||
@ -363,7 +384,7 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
||||
val distribution = HashMap<Connection, MutableList<InventoryVector>>()
|
||||
for ((connection, _) in connections) {
|
||||
if (connection.state == Connection.State.ACTIVE) {
|
||||
distribution.put(connection, mutableListOf<InventoryVector>())
|
||||
distribution[connection] = mutableListOf()
|
||||
}
|
||||
}
|
||||
if (distribution.isEmpty()) {
|
||||
@ -382,7 +403,8 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
||||
}
|
||||
}
|
||||
if (connection.knowsOf(next) && !connection.requested(next)) {
|
||||
val ivs = distribution[connection] ?: throw IllegalStateException("distribution not available for $connection")
|
||||
val ivs = distribution[connection]
|
||||
?: throw IllegalStateException("distribution not available for $connection")
|
||||
if (ivs.size == GetData.MAX_INVENTORY_SIZE) {
|
||||
connection.send(GetData(ivs))
|
||||
ivs.clear()
|
||||
@ -406,7 +428,8 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
||||
}
|
||||
|
||||
for (connection in distribution.keys) {
|
||||
val ivs = distribution[connection] ?: throw IllegalStateException("distribution not available for $connection")
|
||||
val ivs =
|
||||
distribution[connection] ?: throw IllegalStateException("distribution not available for $connection")
|
||||
if (!ivs.isEmpty()) {
|
||||
connection.send(GetData(ivs))
|
||||
}
|
||||
@ -434,12 +457,16 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
||||
for (stream in streams) {
|
||||
val incoming = incomingConnections[stream] ?: 0
|
||||
val outgoing = outgoingConnections[stream] ?: 0
|
||||
streamProperties.add(Property("stream " + stream, Property("nodes", incoming + outgoing),
|
||||
Property("incoming", incoming),
|
||||
Property("outgoing", outgoing)
|
||||
))
|
||||
streamProperties.add(
|
||||
Property(
|
||||
"stream $stream", Property("nodes", incoming + outgoing),
|
||||
Property("incoming", incoming),
|
||||
Property("outgoing", outgoing)
|
||||
)
|
||||
)
|
||||
}
|
||||
return Property("network",
|
||||
return Property(
|
||||
"network",
|
||||
Property("connectionManager", if (isRunning) "running" else "stopped"),
|
||||
Property("connections", streamProperties),
|
||||
Property("requestedObjects", requestedObjects.size)
|
||||
@ -453,8 +480,8 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
|
||||
|
||||
companion object {
|
||||
private val LOG = LoggerFactory.getLogger(NioNetworkHandler::class.java)
|
||||
private val REQUESTED_OBJECTS_MAX_TIME = (2 * 60000).toLong() // 2 minutes in ms
|
||||
private val DELAYED = java.lang.Long.MIN_VALUE
|
||||
private const val REQUESTED_OBJECTS_MAX_TIME = 2 * 60000L // 2 minutes in ms
|
||||
private const val DELAYED = java.lang.Long.MIN_VALUE
|
||||
|
||||
private fun write(channel: SocketChannel, connection: ConnectionIO) {
|
||||
writeBuffer(connection.outBuffers, channel)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user