🐛 Fix connectivity issues and improve code
This commit is contained in:
@ -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
|
||||
|
@ -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++
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user