🐛 Fix connectivity issues and improve code

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

View File

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

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.
*/
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)
this.dataBuffer = BufferPool.allocate(length).apply {
clear()
limit(length)
data(this)
}
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
}
}