🐛 Fix connectivity issues and improve code

This commit is contained in:
Christian Basler 2018-10-15 10:49:04 +02:00
parent fe9fa0ba2f
commit 519f457476
13 changed files with 152 additions and 313 deletions

View File

@ -1,5 +1,5 @@
buildscript { buildscript {
ext.kotlin_version = '1.2.41' ext.kotlin_version = '1.2.71'
repositories { repositories {
mavenCentral() mavenCentral()
} }
@ -31,8 +31,8 @@ subprojects {
jcenter() jcenter()
} }
dependencies { dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk7" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7"
compile "org.jetbrains.kotlin:kotlin-reflect" implementation "org.jetbrains.kotlin:kotlin-reflect"
} }
test { test {
@ -139,21 +139,21 @@ subprojects {
} }
dependency 'ch.dissem.msgpack:msgpack:2.0.1' dependency 'ch.dissem.msgpack:msgpack:2.0.1'
dependency 'org.bouncycastle:bcprov-jdk15on:1.59' dependency 'org.bouncycastle:bcprov-jdk15on:1.60'
dependency 'com.madgag.spongycastle:prov:1.58.0.0' dependency 'com.madgag.spongycastle:prov:1.58.0.0'
dependency 'org.apache.commons:commons-text:1.2' dependency 'org.apache.commons:commons-text:1.5'
dependency 'org.flywaydb:flyway-core:5.0.7' dependency 'org.flywaydb:flyway-core:5.2.0'
dependency 'com.beust:klaxon:2.1.7' dependency 'com.beust:klaxon:3.0.8'
dependency 'args4j:args4j:2.33' dependency 'args4j:args4j:2.33'
dependency 'org.ini4j:ini4j:0.5.4' dependency 'org.ini4j:ini4j:0.5.4'
dependency 'com.h2database:h2:1.4.196' dependency 'com.h2database:h2:1.4.197'
dependency 'org.hamcrest:java-hamcrest:2.0.0.0' dependency 'org.hamcrest:java-hamcrest:2.0.0.0'
dependency 'com.nhaarman:mockito-kotlin:1.5.0' dependency 'com.nhaarman:mockito-kotlin:1.6.0'
dependency 'org.junit.jupiter:junit-jupiter-api:5.2.0' dependency 'org.junit.jupiter:junit-jupiter-api:5.3.1'
dependency 'org.junit.jupiter:junit-jupiter-engine:5.2.0' dependency 'org.junit.jupiter:junit-jupiter-engine:5.3.1'
} }
} }
} }

View File

@ -39,36 +39,26 @@ data class ObjectMessage(
var nonce: ByteArray? = null, var nonce: ByteArray? = null,
val expiresTime: Long, val expiresTime: Long,
val payload: ObjectPayload, val payload: ObjectPayload,
val type: Long, val type: Long = payload.type?.number ?: throw IllegalArgumentException("payload must have type defined"),
/** /**
* The object's version * The object's version
*/ */
val version: Long, val version: Long = payload.version,
val stream: Long val stream: Long = payload.stream
) : MessagePayload { ) : MessagePayload {
override val command: MessagePayload.Command = MessagePayload.Command.OBJECT override val command: MessagePayload.Command = MessagePayload.Command.OBJECT
constructor(
nonce: ByteArray? = null,
expiresTime: Long,
payload: ObjectPayload,
stream: Long
) : this(
nonce,
expiresTime,
payload,
payload.type?.number ?: throw IllegalArgumentException("payload must have type defined"),
payload.version,
stream
)
val inventoryVector: InventoryVector val inventoryVector: InventoryVector
get() { get() {
return InventoryVector(Bytes.truncate(cryptography().doubleSha512( return InventoryVector(
nonce ?: throw IllegalStateException("nonce must be set"), Bytes.truncate(
payloadBytesWithoutNonce cryptography().doubleSha512(
), 32)) nonce ?: throw IllegalStateException("nonce must be set"),
payloadBytesWithoutNonce
), 32
)
)
} }
private val isEncrypted: Boolean private val isEncrypted: Boolean

View File

@ -1,145 +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 ch.dissem.bitmessage.exception.NodeException
import org.slf4j.LoggerFactory
import java.nio.ByteBuffer
import java.util.*
import kotlin.math.max
/**
* 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 var limit: Int? = null
private var strictLimit = false
/**
* Sets a limit to how many buffers the pool handles. If strict is set to true, it will not issue any
* buffers once the limit is reached and will throw a NodeException instead. Otherwise, it will simply
* ignore returned buffers once the limit is reached (and therefore garbage collected)
*/
fun setLimit(limit: Int, strict: Boolean = false) {
this.limit = limit
this.strictLimit = strict
pools.values.forEach { it.limit = limit }
pools[HEADER_SIZE]!!.limit = 2 * limit
pools[MAX_PAYLOAD_SIZE]!!.limit = max(limit / 2, 1)
}
private val pools = mapOf(
HEADER_SIZE to Pool(),
54 to Pool(),
1000 to Pool(),
60000 to Pool(),
MAX_PAYLOAD_SIZE to Pool()
)
@Synchronized
fun allocate(capacity: Int): ByteBuffer {
val targetSize = getTargetSize(capacity)
val pool = pools[targetSize] ?: throw IllegalStateException("No pool for size $targetSize available")
return if (pool.isEmpty) {
if (pool.hasCapacity || !strictLimit) {
LOG.trace("Creating new buffer of size $targetSize")
ByteBuffer.allocate(targetSize)
} else {
throw NodeException("pool limit for capacity $capacity is reached")
}
} else {
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] ?: throw IllegalStateException("No pool for header available")
return if (pool.isEmpty) {
if (pool.hasCapacity || !strictLimit) {
LOG.trace("Creating new buffer of header")
ByteBuffer.allocate(HEADER_SIZE)
} else {
throw NodeException("pool limit for header buffer is reached")
}
} else {
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")
}
/**
* There is a race condition where the limit could be ignored for an allocation, but I think the consequences
* are benign.
*/
class Pool {
private val stack = Stack<ByteBuffer>()
private var capacity = 0
internal var limit: Int? = null
set(value) {
capacity = value ?: 0
field = value
}
val isEmpty
get() = stack.isEmpty()
val hasCapacity
@Synchronized
get() = limit == null || capacity > 0
@Synchronized
fun pop(): ByteBuffer {
capacity--
return stack.pop()
}
@Synchronized
fun push(buffer: ByteBuffer) {
if (hasCapacity) {
stack.push(buffer)
}
// else, let it be collected by the garbage collector
capacity++
}
}
}

View File

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

View File

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

View File

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

View File

@ -115,6 +115,7 @@ public class Application {
System.out.println(ctx.status()); System.out.println(ctx.status());
System.out.println(); System.out.println();
System.out.println("c) cleanup inventory"); System.out.println("c) cleanup inventory");
System.out.println("n) remove known nodes");
System.out.println("r) resend unacknowledged messages"); System.out.println("r) resend unacknowledged messages");
System.out.println(COMMAND_BACK); System.out.println(COMMAND_BACK);
@ -123,6 +124,9 @@ public class Application {
case "c": case "c":
ctx.cleanup(); ctx.cleanup();
break; break;
case "n":
ctx.internals().getNodeRegistry().cleanup();
break;
case "r": case "r":
ctx.resendUnacknowledgedMessages(); ctx.resendUnacknowledgedMessages();
break; break;

View File

@ -63,6 +63,7 @@ public class Main {
.inventory(new JdbcInventory(jdbcConfig)) .inventory(new JdbcInventory(jdbcConfig))
.messageRepo(new JdbcMessageRepository(jdbcConfig)) .messageRepo(new JdbcMessageRepository(jdbcConfig))
.powRepo(new JdbcProofOfWorkRepository(jdbcConfig)) .powRepo(new JdbcProofOfWorkRepository(jdbcConfig))
.labelRepo(new JdbcLabelRepository(jdbcConfig))
.networkHandler(new NioNetworkHandler()) .networkHandler(new NioNetworkHandler())
.cryptography(new BouncyCryptography()); .cryptography(new BouncyCryptography());
ctxBuilder.getPreferences().setPort(48444); ctxBuilder.getPreferences().setPort(48444);

View File

@ -26,6 +26,7 @@ import ch.dissem.bitmessage.ports.Labeler
import ch.dissem.bitmessage.repository.* import ch.dissem.bitmessage.repository.*
import ch.dissem.bitmessage.utils.TTL import ch.dissem.bitmessage.utils.TTL
import ch.dissem.bitmessage.utils.UnixTime.MINUTE import ch.dissem.bitmessage.utils.UnixTime.MINUTE
import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.spy import com.nhaarman.mockito_kotlin.spy
import com.nhaarman.mockito_kotlin.timeout import com.nhaarman.mockito_kotlin.timeout
import com.nhaarman.mockito_kotlin.verify import com.nhaarman.mockito_kotlin.verify
@ -34,7 +35,6 @@ import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTimeoutPreemptively import org.junit.jupiter.api.Assertions.assertTimeoutPreemptively
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.mockito.ArgumentMatchers.any
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.time.Duration.ofMinutes import java.time.Duration.ofMinutes
import java.time.Duration.ofSeconds import java.time.Duration.ofSeconds
@ -126,7 +126,7 @@ class SystemTest {
verify( verify(
aliceLabeler, aliceLabeler,
timeout(TimeUnit.MINUTES.toMillis(2)).atLeastOnce() timeout(TimeUnit.MINUTES.toMillis(2)).atLeastOnce()
).markAsAcknowledged(any<Plaintext>()) ).markAsAcknowledged(any())
} }
} }
@ -145,7 +145,7 @@ class SystemTest {
} }
} }
internal class DebugLabeler internal constructor(private val name: String) : DefaultLabeler() { internal open class DebugLabeler internal constructor(private val name: String) : DefaultLabeler() {
private val LOG = LoggerFactory.getLogger("Labeler") private val LOG = LoggerFactory.getLogger("Labeler")
private lateinit var alice: String private lateinit var alice: String
private lateinit var bob: String private lateinit var bob: String

View File

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

View File

@ -21,7 +21,6 @@ import ch.dissem.bitmessage.entity.GetData
import ch.dissem.bitmessage.entity.MessagePayload import ch.dissem.bitmessage.entity.MessagePayload
import ch.dissem.bitmessage.entity.NetworkMessage import ch.dissem.bitmessage.entity.NetworkMessage
import ch.dissem.bitmessage.entity.valueobject.InventoryVector import ch.dissem.bitmessage.entity.valueobject.InventoryVector
import ch.dissem.bitmessage.exception.NodeException
import ch.dissem.bitmessage.factory.V3MessageReader import ch.dissem.bitmessage.factory.V3MessageReader
import ch.dissem.bitmessage.utils.UnixTime import ch.dissem.bitmessage.utils.UnixTime
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@ -42,7 +41,7 @@ class ConnectionIO(
) { ) {
private val headerOut: ByteBuffer = ByteBuffer.allocate(HEADER_SIZE) private val headerOut: ByteBuffer = ByteBuffer.allocate(HEADER_SIZE)
private var payloadOut: ByteBuffer? = null private var payloadOut: ByteBuffer? = null
private var reader: V3MessageReader? = V3MessageReader() private val reader = V3MessageReader()
internal val sendingQueue: Deque<MessagePayload> = ConcurrentLinkedDeque<MessagePayload>() internal val sendingQueue: Deque<MessagePayload> = ConcurrentLinkedDeque<MessagePayload>()
internal var lastUpdate = System.currentTimeMillis() internal var lastUpdate = System.currentTimeMillis()
@ -55,8 +54,7 @@ class ConnectionIO(
headerOut.flip() headerOut.flip()
} }
val inBuffer: ByteBuffer val inBuffer: ByteBuffer = reader.buffer
get() = reader?.getActiveBuffer() ?: throw NodeException("Node is disconnected")
fun updateWriter() { fun updateWriter() {
if (!headerOut.hasRemaining() && !sendingQueue.isEmpty()) { if (!headerOut.hasRemaining() && !sendingQueue.isEmpty()) {
@ -78,7 +76,7 @@ class ConnectionIO(
} }
fun updateReader() { fun updateReader() {
reader?.let { reader -> reader.let { reader ->
reader.update() reader.update()
if (!reader.getMessages().isEmpty()) { if (!reader.getMessages().isEmpty()) {
val iterator = reader.getMessages().iterator() val iterator = reader.getMessages().iterator()
@ -96,11 +94,11 @@ class ConnectionIO(
fun updateSyncStatus() { fun updateSyncStatus() {
if (!isSyncFinished) { if (!isSyncFinished) {
isSyncFinished = reader?.getMessages()?.isEmpty() ?: true && syncFinished(null) isSyncFinished = reader.getMessages().isEmpty() && syncFinished(null)
} }
} }
protected fun syncFinished(msg: NetworkMessage?): Boolean { private fun syncFinished(msg: NetworkMessage?): Boolean {
if (mode != Connection.Mode.SYNC) { if (mode != Connection.Mode.SYNC) {
return false return false
} }
@ -127,10 +125,6 @@ class ConnectionIO(
} }
fun disconnect() { fun disconnect() {
reader?.let {
it.cleanup()
reader = null
}
payloadOut = null payloadOut = null
} }
@ -151,7 +145,7 @@ class ConnectionIO(
|| headerOut.hasRemaining() || headerOut.hasRemaining()
|| payloadOut?.hasRemaining() ?: false || payloadOut?.hasRemaining() ?: false
fun nothingToSend() = sendingQueue.isEmpty() private fun nothingToSend() = sendingQueue.isEmpty()
companion object { companion object {
val LOG = LoggerFactory.getLogger(ConnectionIO::class.java) val LOG = LoggerFactory.getLogger(ConnectionIO::class.java)

View File

@ -77,12 +77,12 @@ class NetworkConnectionInitializer(
activateConnection() activateConnection()
} }
} else { } else {
throw NodeException("Received unsupported version " + version.version + ", disconnecting.") throw NodeException("Received unsupported version ${version.version}, disconnecting.")
} }
} }
private fun activateConnection() { private fun activateConnection() {
LOG.info("Successfully established connection with node " + node) LOG.info("Successfully established connection with node $node")
markActive(version.streams) markActive(version.streams)
node.time = UnixTime.now node.time = UnixTime.now
if (mode != Connection.Mode.SYNC) { if (mode != Connection.Mode.SYNC) {

View File

@ -17,6 +17,7 @@
package ch.dissem.bitmessage.networking.nio package ch.dissem.bitmessage.networking.nio
import ch.dissem.bitmessage.InternalContext import ch.dissem.bitmessage.InternalContext
import ch.dissem.bitmessage.constants.Network.HEADER_SIZE
import ch.dissem.bitmessage.constants.Network.NETWORK_MAGIC_NUMBER import ch.dissem.bitmessage.constants.Network.NETWORK_MAGIC_NUMBER
import ch.dissem.bitmessage.entity.CustomMessage import ch.dissem.bitmessage.entity.CustomMessage
import ch.dissem.bitmessage.entity.GetData import ch.dissem.bitmessage.entity.GetData
@ -24,7 +25,6 @@ import ch.dissem.bitmessage.entity.NetworkMessage
import ch.dissem.bitmessage.entity.valueobject.InventoryVector import ch.dissem.bitmessage.entity.valueobject.InventoryVector
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress import ch.dissem.bitmessage.entity.valueobject.NetworkAddress
import ch.dissem.bitmessage.exception.NodeException import ch.dissem.bitmessage.exception.NodeException
import ch.dissem.bitmessage.factory.BufferPool
import ch.dissem.bitmessage.factory.V3MessageReader import ch.dissem.bitmessage.factory.V3MessageReader
import ch.dissem.bitmessage.networking.nio.Connection.Mode.* import ch.dissem.bitmessage.networking.nio.Connection.Mode.*
import ch.dissem.bitmessage.ports.NetworkHandler import ch.dissem.bitmessage.ports.NetworkHandler
@ -94,29 +94,26 @@ class NioNetworkHandler(private val magicNetworkNumber: Int = NETWORK_MAGIC_NUMB
override fun send(server: InetAddress, port: Int, request: CustomMessage): CustomMessage { override fun send(server: InetAddress, port: Int, request: CustomMessage): CustomMessage {
SocketChannel.open(InetSocketAddress(server, port)).use { channel -> SocketChannel.open(InetSocketAddress(server, port)).use { channel ->
channel.configureBlocking(true) channel.configureBlocking(true)
val headerBuffer = BufferPool.allocateHeaderBuffer() val headerBuffer = ByteBuffer.allocate(HEADER_SIZE)
val payloadBuffer = NetworkMessage(request).writer().writeHeaderAndGetPayloadBuffer(headerBuffer) val payloadBuffer = NetworkMessage(request).writer().writeHeaderAndGetPayloadBuffer(headerBuffer)
headerBuffer.flip() headerBuffer.flip()
while (headerBuffer.hasRemaining()) { while (headerBuffer.hasRemaining()) {
channel.write(headerBuffer) channel.write(headerBuffer)
} }
BufferPool.deallocate(headerBuffer)
while (payloadBuffer.hasRemaining()) { while (payloadBuffer.hasRemaining()) {
channel.write(payloadBuffer) channel.write(payloadBuffer)
} }
val reader = V3MessageReader() val reader = V3MessageReader()
while (channel.isConnected && reader.getMessages().isEmpty()) { while (channel.isConnected && reader.getMessages().isEmpty()) {
if (channel.read(reader.getActiveBuffer()) > 0) { if (channel.read(reader.buffer) > 0) {
reader.update() reader.update()
} else { } else {
reader.cleanup()
throw NodeException("No response from node $server") throw NodeException("No response from node $server")
} }
} }
val networkMessage: NetworkMessage? val networkMessage: NetworkMessage?
if (reader.getMessages().isEmpty()) { if (reader.getMessages().isEmpty()) {
reader.cleanup()
throw NodeException("No response from node $server") throw NodeException("No response from node $server")
} else { } else {
networkMessage = reader.getMessages().first() networkMessage = reader.getMessages().first()
@ -125,7 +122,6 @@ class NioNetworkHandler(private val magicNetworkNumber: Int = NETWORK_MAGIC_NUMB
if (networkMessage.payload is CustomMessage) { if (networkMessage.payload is CustomMessage) {
return networkMessage.payload as CustomMessage return networkMessage.payload as CustomMessage
} else { } else {
reader.cleanup()
throw NodeException("Unexpected response from node $server: ${networkMessage.payload.javaClass}") throw NodeException("Unexpected response from node $server: ${networkMessage.payload.javaClass}")
} }
} }
@ -196,14 +192,14 @@ class NioNetworkHandler(private val magicNetworkNumber: Int = NETWORK_MAGIC_NUMB
request(delayed) request(delayed)
try { try {
Thread.sleep(30000) Thread.sleep(10000)
} catch (e: InterruptedException) { } catch (e: InterruptedException) {
return@thread return@thread
} }
} }
} }
thread("selector worker", { thread("selector worker") {
try { try {
val serverChannel = ServerSocketChannel.open() val serverChannel = ServerSocketChannel.open()
this.serverChannel = serverChannel this.serverChannel = serverChannel
@ -272,10 +268,13 @@ class NioNetworkHandler(private val magicNetworkNumber: Int = NETWORK_MAGIC_NUMB
else -> key.interestOps(OP_READ) else -> key.interestOps(OP_READ)
} }
} catch (e: CancelledKeyException) { } catch (e: CancelledKeyException) {
LOG.debug("${e.message}: ${connection.node}", e)
connection.disconnect() connection.disconnect()
} catch (e: NodeException) { } catch (e: NodeException) {
LOG.debug("${e.message}: ${connection.node}", e)
connection.disconnect() connection.disconnect()
} catch (e: IOException) { } catch (e: IOException) {
LOG.debug("${e.message}: ${connection.node}", e)
connection.disconnect() connection.disconnect()
} }
} }
@ -306,10 +305,11 @@ class NioNetworkHandler(private val magicNetworkNumber: Int = NETWORK_MAGIC_NUMB
channel.configureBlocking(false) channel.configureBlocking(false)
channel.connect(InetSocketAddress(address.toInetAddress(), address.port)) channel.connect(InetSocketAddress(address.toInetAddress(), address.port))
val connection = Connection(ctx, CLIENT, address, requestedObjects, 0) val connection = Connection(ctx, CLIENT, address, requestedObjects, 0)
connections.put(
connection, connections[connection] = channel.register(selector, OP_CONNECT, connection)
channel.register(selector, OP_CONNECT, connection)
) LOG.debug("Connection registered to $address")
} catch (ignore: NoRouteToHostException) { } catch (ignore: NoRouteToHostException) {
// We'll try to connect to many offline nodes, so // We'll try to connect to many offline nodes, so
// this is expected to happen quite a lot. // this is expected to happen quite a lot.
@ -321,7 +321,11 @@ class NioNetworkHandler(private val magicNetworkNumber: Int = NETWORK_MAGIC_NUMB
LOG.error(e.message, e) LOG.error(e.message, e)
} }
} catch (e: IOException) { } catch (e: IOException) {
LOG.error(e.message, e) if (e.message == "Network is unreachable") {
LOG.debug("Network is unreachable: $address")
} else {
LOG.error("${e.message}: $address", e)
}
} }
} }
@ -335,7 +339,7 @@ class NioNetworkHandler(private val magicNetworkNumber: Int = NETWORK_MAGIC_NUMB
// isn't nice though. // isn't nice though.
LOG.error(e.message, e) LOG.error(e.message, e)
} }
}) }
} }
private fun thread(threadName: String, runnable: () -> Unit): Thread { private fun thread(threadName: String, runnable: () -> Unit): Thread {
@ -380,7 +384,7 @@ class NioNetworkHandler(private val magicNetworkNumber: Int = NETWORK_MAGIC_NUMB
val distribution = HashMap<Connection, MutableList<InventoryVector>>() val distribution = HashMap<Connection, MutableList<InventoryVector>>()
for ((connection, _) in connections) { for ((connection, _) in connections) {
if (connection.state == Connection.State.ACTIVE) { if (connection.state == Connection.State.ACTIVE) {
distribution.put(connection, mutableListOf<InventoryVector>()) distribution[connection] = mutableListOf()
} }
} }
if (distribution.isEmpty()) { if (distribution.isEmpty()) {
@ -455,7 +459,7 @@ class NioNetworkHandler(private val magicNetworkNumber: Int = NETWORK_MAGIC_NUMB
val outgoing = outgoingConnections[stream] ?: 0 val outgoing = outgoingConnections[stream] ?: 0
streamProperties.add( streamProperties.add(
Property( Property(
"stream " + stream, Property("nodes", incoming + outgoing), "stream $stream", Property("nodes", incoming + outgoing),
Property("incoming", incoming), Property("incoming", incoming),
Property("outgoing", outgoing) Property("outgoing", outgoing)
) )
@ -476,8 +480,8 @@ class NioNetworkHandler(private val magicNetworkNumber: Int = NETWORK_MAGIC_NUMB
companion object { companion object {
private val LOG = LoggerFactory.getLogger(NioNetworkHandler::class.java) private val LOG = LoggerFactory.getLogger(NioNetworkHandler::class.java)
private val REQUESTED_OBJECTS_MAX_TIME = (2 * 60000).toLong() // 2 minutes in ms private const val REQUESTED_OBJECTS_MAX_TIME = 2 * 60000L // 2 minutes in ms
private val DELAYED = java.lang.Long.MIN_VALUE private const val DELAYED = java.lang.Long.MIN_VALUE
private fun write(channel: SocketChannel, connection: ConnectionIO) { private fun write(channel: SocketChannel, connection: ConnectionIO) {
writeBuffer(connection.outBuffers, channel) writeBuffer(connection.outBuffers, channel)